Skip to content

Commit

Permalink
List programs (#3320)
Browse files Browse the repository at this point in the history
Exposes the listing of quantum programs on the Engine API.

One half of #3302.
  • Loading branch information
balopat committed Sep 15, 2020
1 parent 0f29fae commit bfedcbc
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 1 deletion.
36 changes: 36 additions & 0 deletions cirq/google/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,42 @@ def get_program(self, program_id: str) -> engine_program.EngineProgram:
return engine_program.EngineProgram(self.project_id, program_id,
self.context)

def list_programs(self,
created_before: Optional[
Union[datetime.datetime, datetime.date]] = None,
created_after: Optional[
Union[datetime.datetime, datetime.date]] = None,
has_labels: Optional[Dict[str, str]] = None
) -> List[engine_program.EngineProgram]:
"""Returns a list of previously executed quantum programs.
Args:
created_after: retrieve programs that were created after this date
or time.
created_before: retrieve programs that were created after this date
or time.
has_labels: retrieve programs that have labels on them specified by
this dict. If the value is set to `*`, filters having the label
regardless of the label value will be filtered. For example, to
query programs that have the shape label and have the color
label with value red can be queried using
`{'color: red', 'shape:*'}`
"""

client = self.context.client
response = client.list_programs(self.project_id,
created_before=created_before,
created_after=created_after,
has_labels=has_labels)
return [
engine_program.EngineProgram(
project_id=client._ids_from_program_name(p.name)[0],
program_id=client._ids_from_program_name(p.name)[1],
_program=p,
context=self.context,
) for p in response
]

def list_processors(self) -> List[engine_processor.EngineProcessor]:
"""Returns a list of Processors that the user has visibility to in the
current Engine project. The names of these processors are used to
Expand Down
51 changes: 50 additions & 1 deletion cirq/google/engine/engine_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
import datetime
import sys
import time
from typing import Callable, Dict, List, Optional, Sequence, TypeVar, Tuple
from typing import Callable, Dict, List, Optional, Sequence, TypeVar, Tuple, \
Union
import warnings

from google.api_core.exceptions import GoogleAPICallError, NotFound
Expand Down Expand Up @@ -199,6 +200,54 @@ def get_program(self, project_id: str, program_id: str,
return self._make_request(lambda: self.grpc_client.get_quantum_program(
self._program_name_from_ids(project_id, program_id), return_code))

def list_programs(self,
project_id: str,
created_before: Optional[
Union[datetime.datetime, datetime.date]] = None,
created_after: Optional[
Union[datetime.datetime, datetime.date]] = None,
has_labels: Optional[Dict[str, str]] = None):
"""Returns a list of previously executed quantum programs.
Args:
project_id: the id of the project
created_after: retrieve programs that were created after this date
or time.
created_before: retrieve programs that were created after this date
or time.
has_labels: retrieve programs that have labels on them specified by
this dict. If the value is set to `*`, filters having the label
regardless of the label value will be filtered. For example, to
query programs that have the shape label and have the color
label with value red can be queried using
`{'color: red', 'shape:*'}`
"""
filters = []

def _to_filter_date_or_time(arg_name, arg):
if isinstance(arg, datetime.datetime):
return f"{int(arg.timestamp())}"
elif isinstance(arg, datetime.date):
return f"{arg.isoformat()}"

raise ValueError(
f"Unsupported date/time type for {arg_name}: got {arg} of "
f"type {type(arg)}. Supported types: datetime.datetime and"
f"datetime.date")

if created_after is not None:
val = _to_filter_date_or_time('created_after', created_after)
filters.append(f"create_time >= {val}")
if created_before is not None:
val = _to_filter_date_or_time('created_before', created_before)
filters.append(f"create_time <= {val}")
if has_labels is not None:
for (k, v) in has_labels.items():
filters.append(f"labels.{k}:{v}")
return self._make_request(
lambda: self.grpc_client.list_quantum_programs(
self._project_name(project_id), filter_=" AND ".join(filters)))

def set_program_description(self, project_id: str, program_id: str,
description: str) -> qtypes.QuantumProgram:
"""Sets the description for a previously created quantum program.
Expand Down
85 changes: 85 additions & 0 deletions cirq/google/engine/engine_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,91 @@ def test_get_program(client_constructor):
'projects/proj/programs/prog', True)


@mock.patch.object(quantum, 'QuantumEngineServiceClient', autospec=True)
def test_list_program(client_constructor):
grpc_client = setup_mock_(client_constructor)

results = [
qtypes.QuantumProgram(name='projects/proj/programs/prog1'),
qtypes.QuantumProgram(name='projects/proj/programs/prog2')
]
grpc_client.list_quantum_programs.return_value = results

client = EngineClient()
assert client.list_programs(project_id='proj') == results
assert grpc_client.list_quantum_programs.call_args[0] == ('projects/proj',)
assert grpc_client.list_quantum_programs.call_args[1] == {
'filter_': '',
}


# yapf: disable
@pytest.mark.parametrize(
'expected_filter, created_after, created_before, labels',
[
('',
None,
None,
None),
('create_time >= 2020-09-01',
datetime.date(2020, 9, 1),
None,
None),
('create_time >= 1598918400',
datetime.datetime(2020, 9, 1, 0, 0, 0,
tzinfo=datetime.timezone.utc),
None,
None),
('create_time <= 2020-10-01',
None,
datetime.date(2020, 10, 1),
None),
('create_time >= 2020-09-01 AND create_time <= 1598918410',
datetime.date(2020, 9, 1),
datetime.datetime(2020, 9, 1, 0, 0, 10,
tzinfo=datetime.timezone.utc),
None),
('labels.color:red AND labels.shape:*',
None,
None,
{
'color': 'red',
'shape': '*'
},
),
('create_time >= 2020-08-01 AND '
'create_time <= 1598918400 AND '
'labels.color:red AND labels.shape:*',
datetime.date(2020, 8, 1),
datetime.datetime(2020, 9, 1, tzinfo=datetime.timezone.utc),
{
'color': 'red',
'shape': '*'
},
),
])
# yapf: enable
@mock.patch.object(quantum, 'QuantumEngineServiceClient', autospec=True)
def test_list_program_filters(client_constructor, expected_filter,
created_before, created_after, labels):
grpc_client = setup_mock_(client_constructor)
client = EngineClient()
client.list_programs(project_id='proj',
created_before=created_before,
created_after=created_after,
has_labels=labels)
assert grpc_client.list_quantum_programs.call_args[1] == {
'filter_': expected_filter,
}


@mock.patch.object(quantum, 'QuantumEngineServiceClient', autospec=True)
def test_list_program_filters_invalid_type(client_constructor):
with pytest.raises(ValueError, match=""):
EngineClient().list_programs(project_id='proj',
created_before="Unsupported date/time")


@mock.patch.object(quantum, 'QuantumEngineServiceClient', autospec=True)
def test_set_program_description(client_constructor):
grpc_client = setup_mock_(client_constructor)
Expand Down
18 changes: 18 additions & 0 deletions cirq/google/engine/engine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,24 @@ def test_get_program():
assert cg.Engine(project_id='proj').get_program('prog').program_id == 'prog'


@mock.patch('cirq.google.engine.engine_client.EngineClient.list_programs')
def test_list_programs(list_programs):
prog1 = qtypes.QuantumProgram(
name='projects/proj/programs/prog-YBGR48THF3JHERZW200804')
prog2 = qtypes.QuantumProgram(
name='projects/otherproj/programs/prog-V3ZRTV6TTAFNTYJV200804')
list_programs.return_value = [prog1, prog2]

result = cg.Engine(project_id='proj').list_programs()
list_programs.assert_called_once_with('proj',
created_after=None,
created_before=None,
has_labels=None)
assert [(p.program_id, p.project_id, p._program) for p in result
] == [('prog-YBGR48THF3JHERZW200804', 'proj', prog1),
('prog-V3ZRTV6TTAFNTYJV200804', 'otherproj', prog2)]


@mock.patch('cirq.google.engine.engine_client.EngineClient')
def test_create_program(client):
client().create_program.return_value = ('prog', qtypes.QuantumProgram())
Expand Down

0 comments on commit bfedcbc

Please sign in to comment.