Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3855677
EngineJob
mpharrigan Mar 26, 2022
0e4446c
Merge remote-tracking branch 'origin/master' into 2022-03-beef-result
mpharrigan Apr 19, 2022
848b261
Job not optional, misc fixes, test fixes
mpharrigan Apr 19, 2022
addcadf
type check
mpharrigan Apr 19, 2022
939e880
add test
mpharrigan Apr 19, 2022
992263a
json serialization blah
mpharrigan Apr 19, 2022
8fb3405
Merge remote-tracking branch 'origin/master' into 2022-03-beef-result
mpharrigan Apr 20, 2022
5eed869
backwards compat
mpharrigan Apr 20, 2022
bf3e8e6
pylint
mpharrigan Apr 20, 2022
31baa9d
coverage
mpharrigan Apr 20, 2022
ca9e96b
Serialize datetime
mpharrigan Apr 20, 2022
598f2ff
Merge branch '2022-04-datetime' into 2022-03-beef-result
mpharrigan Apr 20, 2022
180695b
Regenerate json data with timestamp
mpharrigan Apr 20, 2022
c2c914e
Improve testing, 'aware' timestamps
mpharrigan Apr 20, 2022
7184771
Allow any "aware" tz during serialization
mpharrigan Apr 20, 2022
bce0412
_ut to _dt
mpharrigan Apr 21, 2022
6557c75
Merge branch 'master' into 2022-04-datetime
CirqBot Apr 21, 2022
ca96175
Merge branch 'master' into 2022-04-datetime
CirqBot Apr 21, 2022
85bfd0e
Merge branch '2022-04-datetime' into 2022-03-beef-result
mpharrigan Apr 21, 2022
787fdbd
Merge remote-tracking branch 'origin/master' into 2022-03-beef-result
mpharrigan Apr 21, 2022
64bae1a
mocky mocky
mpharrigan Apr 22, 2022
9ebeb2f
Merge remote-tracking branch 'origin/master' into 2022-03-beef-result
mpharrigan Apr 25, 2022
c7471dd
review comments
mpharrigan Apr 25, 2022
ef2fcdf
format
mpharrigan Apr 25, 2022
a55fd2c
don't convert input datetime to engineresult
mpharrigan Apr 28, 2022
e8bb157
Merge remote-tracking branch 'origin/master' into 2022-03-beef-result
mpharrigan Apr 28, 2022
5440a0b
Merge branch 'master' into 2022-03-beef-result
CirqBot Apr 28, 2022
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
19 changes: 11 additions & 8 deletions cirq-core/cirq/study/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,12 @@ def data(self) -> pd.DataFrame:
self._data = self.dataframe_from_measurements(self.measurements)
return self._data

def _record_dict_repr(self):
"""Helper function for use in __repr__ to display the records field."""
return '{' + ', '.join(f'{k!r}: {proper_repr(v)}' for k, v in self.records.items()) + '}'

def __repr__(self) -> str:
record_dict_repr = (
'{' + ', '.join(f'{k!r}: {proper_repr(v)}' for k, v in self.records.items()) + '}'
)
return f'cirq.ResultDict(params={self.params!r}, records={record_dict_repr})'
return f'cirq.ResultDict(params={self.params!r}, records={self._record_dict_repr()})'

def _repr_pretty_(self, p: Any, cycle: bool) -> None:
"""Output to show in ipython and Jupyter notebooks."""
Expand All @@ -435,6 +436,11 @@ def _json_dict_(self):
}
return {'params': self.params, 'records': packed_records}

@classmethod
def _from_packed_records(cls, records, **kwargs):
"""Helper function for `_from_json_dict_` to construct from packed records."""
return cls(records={key: _unpack_digits(**val) for key, val in records.items()}, **kwargs)

@classmethod
def _from_json_dict_(cls, params, **kwargs):
if 'measurements' in kwargs:
Expand All @@ -443,10 +449,7 @@ def _from_json_dict_(cls, params, **kwargs):
params=params,
measurements={key: _unpack_digits(**val) for key, val in measurements.items()},
)
records = kwargs['records']
return cls(
params=params, records={key: _unpack_digits(**val) for key, val in records.items()}
)
return cls._from_packed_records(params=params, records=kwargs['records'])


def _pack_digits(digits: np.ndarray, pack_bits: str = 'auto') -> Tuple[str, bool]:
Expand Down
1 change: 1 addition & 0 deletions cirq-google/cirq_google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
EngineJob,
EngineProgram,
EngineProcessor,
EngineResult,
ProtoVersion,
QuantumEngineSampler,
ValidatingSampler,
Expand Down
2 changes: 2 additions & 0 deletions cirq-google/cirq_google/engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@
create_noiseless_virtual_engine_from_templates,
create_noiseless_virtual_engine_from_latest_templates,
)

from cirq_google.engine.engine_result import EngineResult
5 changes: 3 additions & 2 deletions cirq-google/cirq_google/engine/abstract_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import cirq
from cirq_google.cloud import quantum
from cirq_google.engine.engine_result import EngineResult

if TYPE_CHECKING:
import datetime
Expand Down Expand Up @@ -161,7 +162,7 @@ def delete(self) -> Optional[bool]:
"""Deletes the job and result, if any."""

@abc.abstractmethod
def batched_results(self) -> Sequence[Sequence[cirq.Result]]:
def batched_results(self) -> Sequence[Sequence[EngineResult]]:
"""Returns the job results, blocking until the job is complete.

This method is intended for batched jobs. Instead of flattening
Expand All @@ -170,7 +171,7 @@ def batched_results(self) -> Sequence[Sequence[cirq.Result]]:
"""

@abc.abstractmethod
def results(self) -> Sequence[cirq.Result]:
def results(self) -> Sequence[EngineResult]:
"""Returns the job results, blocking until the job is complete."""

@abc.abstractmethod
Expand Down
5 changes: 3 additions & 2 deletions cirq-google/cirq_google/engine/abstract_local_job_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from cirq_google.cloud import quantum
from cirq_google.engine.calibration_result import CalibrationResult
from cirq_google.engine.abstract_local_job import AbstractLocalJob
from cirq_google.engine.engine_result import EngineResult


class NothingJob(AbstractLocalJob):
Expand All @@ -40,10 +41,10 @@ def cancel(self) -> None:
def delete(self) -> None:
pass

def batched_results(self) -> Sequence[Sequence[cirq.Result]]:
def batched_results(self) -> Sequence[Sequence[EngineResult]]:
return [] # coverage: ignore

def results(self) -> Sequence[cirq.Result]:
def results(self) -> Sequence[EngineResult]:
return [] # coverage: ignore

def calibration_results(self) -> Sequence[CalibrationResult]:
Expand Down
94 changes: 50 additions & 44 deletions cirq-google/cirq_google/engine/engine_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from cirq_google.engine.calibration_result import CalibrationResult
from cirq_google.cloud import quantum
from cirq_google.engine.result_type import ResultType
from cirq_google.engine.engine_result import EngineResult
from cirq_google.api import v1, v2

if TYPE_CHECKING:
Expand All @@ -39,6 +40,10 @@
]


def _flatten(result: Sequence[Sequence[EngineResult]]) -> List[EngineResult]:
return [res for result_list in result for res in result_list]


class EngineJob(abstract_job.AbstractJob):
"""A job created via the Quantum Engine API.

Expand Down Expand Up @@ -81,9 +86,9 @@ def __init__(
self.job_id = job_id
self.context = context
self._job = _job
self._results: Optional[Sequence[cirq.Result]] = None
self._results: Optional[Sequence[EngineResult]] = None
self._calibration_results: Optional[Sequence[CalibrationResult]] = None
self._batched_results: Optional[Sequence[Sequence[cirq.Result]]] = None
self._batched_results: Optional[Sequence[Sequence[EngineResult]]] = None
self.result_type = result_type

def id(self) -> str:
Expand Down Expand Up @@ -122,10 +127,8 @@ def create_time(self) -> 'datetime.datetime':

def update_time(self) -> 'datetime.datetime':
"""Returns when the job was last updated."""
self._job = self.context.client.get_job(
self.project_id, self.program_id, self.job_id, False
)
return self._job.update_time
job = self._refresh_job()
return job.update_time

def description(self) -> str:
"""Returns the description of the job."""
Expand Down Expand Up @@ -257,7 +260,7 @@ def delete(self) -> None:
"""Deletes the job and result, if any."""
self.context.client.delete_job(self.project_id, self.program_id, self.job_id)

def batched_results(self) -> Sequence[Sequence[cirq.Result]]:
def batched_results(self) -> Sequence[Sequence[EngineResult]]:
"""Returns the job results, blocking until the job is complete.

This method is intended for batched jobs. Instead of flattening
Expand Down Expand Up @@ -287,7 +290,7 @@ def _wait_for_result(self):
)
return response.result

def results(self) -> Sequence[cirq.Result]:
def results(self) -> Sequence[EngineResult]:
"""Returns the job results, blocking until the job is complete."""
import cirq_google.engine.engine as engine_base

Expand All @@ -299,17 +302,17 @@ def results(self) -> Sequence[cirq.Result]:
or result_type == 'cirq.api.google.v1.Result'
):
v1_parsed_result = v1.program_pb2.Result.FromString(result.value)
self._results = _get_job_results_v1(v1_parsed_result)
self._results = self._get_job_results_v1(v1_parsed_result) # coverage: ignore
elif (
result_type == 'cirq.google.api.v2.Result'
or result_type == 'cirq.api.google.v2.Result'
):
v2_parsed_result = v2.result_pb2.Result.FromString(result.value)
self._results = _get_job_results_v2(v2_parsed_result)
self._results = self._get_job_results_v2(v2_parsed_result)
elif result.Is(v2.batch_pb2.BatchResult.DESCRIPTOR):
v2_parsed_result = v2.batch_pb2.BatchResult.FromString(result.value)
self._batched_results = self._get_batch_results_v2(v2_parsed_result)
self._results = self._flatten(self._batched_results)
self._results = _flatten(self._batched_results)
else:
raise ValueError(f'invalid result proto version: {result_type}')
return self._results
Expand Down Expand Up @@ -340,19 +343,45 @@ def calibration_results(self) -> Sequence[CalibrationResult]:
self._calibration_results = cal_results
return self._calibration_results

@classmethod
def _get_batch_results_v2(
cls, results: v2.batch_pb2.BatchResult
) -> Sequence[Sequence[cirq.Result]]:
def _get_job_results_v1(self, result: v1.program_pb2.Result) -> Sequence[EngineResult]:
# coverage: ignore
job_id = self.id()
job_finished = self.update_time()

trial_results = []
for result in results.results:
# Add a new list for the result
trial_results.append(_get_job_results_v2(result))
for sweep_result in result.sweep_results:
sweep_repetitions = sweep_result.repetitions
key_sizes = [(m.key, len(m.qubits)) for m in sweep_result.measurement_keys]
for result in sweep_result.parameterized_results:
data = result.measurement_results
measurements = v1.unpack_results(data, sweep_repetitions, key_sizes)

trial_results.append(
EngineResult(
params=cirq.ParamResolver(result.params.assignments),
measurements=measurements,
job_id=job_id,
job_finished_time=job_finished,
)
)
return trial_results

@classmethod
def _flatten(cls, result) -> Sequence[cirq.Result]:
return [res for result_list in result for res in result_list]
def _get_job_results_v2(self, result: v2.result_pb2.Result) -> Sequence[EngineResult]:
sweep_results = v2.results_from_proto(result)
job_id = self.id()
job_finished = self.update_time()

# Flatten to single list to match to sampler api.
return [
EngineResult.from_result(result, job_id=job_id, job_finished_time=job_finished)
for sweep_result in sweep_results
for result in sweep_result
]

def _get_batch_results_v2(
self, results: v2.batch_pb2.BatchResult
) -> Sequence[Sequence[EngineResult]]:
return [self._get_job_results_v2(result) for result in results.results]

def __iter__(self) -> Iterator[cirq.Result]:
return iter(self.results())
Expand Down Expand Up @@ -401,29 +430,6 @@ def _deserialize_run_context(run_context: any_pb2.Any) -> Tuple[int, List[cirq.S
raise ValueError(f'unsupported run_context type: {run_context_type}')


def _get_job_results_v1(result: v1.program_pb2.Result) -> Sequence[cirq.Result]:
trial_results = []
for sweep_result in result.sweep_results:
sweep_repetitions = sweep_result.repetitions
key_sizes = [(m.key, len(m.qubits)) for m in sweep_result.measurement_keys]
for result in sweep_result.parameterized_results:
data = result.measurement_results
measurements = v1.unpack_results(data, sweep_repetitions, key_sizes)

trial_results.append(
cirq.ResultDict(
params=cirq.ParamResolver(result.params.assignments), measurements=measurements
)
)
return trial_results


def _get_job_results_v2(result: v2.result_pb2.Result) -> Sequence[cirq.Result]:
sweep_results = v2.results_from_proto(result)
# Flatten to single list to match to sampler api.
return [trial_result for sweep_result in sweep_results for trial_result in sweep_result]


def _raise_on_failure(job: quantum.QuantumJob) -> None:
execution_status = job.execution_status
state = execution_status.state
Expand Down
32 changes: 22 additions & 10 deletions cirq-google/cirq_google/engine/engine_job_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,11 +501,14 @@ def test_delete(delete_job):
)
)

UPDATE_TIME = datetime.datetime.now(tz=datetime.timezone.utc)


@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_results(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = RESULTS

Expand All @@ -520,7 +523,8 @@ def test_results(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_results_iter(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = RESULTS

Expand All @@ -534,7 +538,8 @@ def test_results_iter(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_results_getitem(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = RESULTS

Expand All @@ -548,7 +553,8 @@ def test_results_getitem(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_batched_results(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = BATCH_RESULTS

Expand All @@ -574,7 +580,8 @@ def test_batched_results(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_batched_results_not_a_batch(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = RESULTS
job = cg.EngineJob('a', 'b', 'steve', EngineContext(), _job=qjob)
Expand All @@ -585,7 +592,8 @@ def test_batched_results_not_a_batch(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_calibration_results(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = CALIBRATION_RESULT
job = cg.EngineJob('a', 'b', 'steve', EngineContext(), _job=qjob)
Expand All @@ -603,7 +611,8 @@ def test_calibration_results(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_calibration_defaults(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
result = v2.calibration_pb2.FocusedCalibrationResult()
result.results.add()
Expand All @@ -622,7 +631,8 @@ def test_calibration_defaults(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_calibration_results_not_a_calibration(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = RESULTS
job = cg.EngineJob('a', 'b', 'steve', EngineContext(), _job=qjob)
Expand All @@ -633,7 +643,8 @@ def test_calibration_results_not_a_calibration(get_job_results):
@mock.patch('cirq_google.engine.engine_client.EngineClient.get_job_results')
def test_results_len(get_job_results):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.SUCCESS),
update_time=UPDATE_TIME,
)
get_job_results.return_value = RESULTS

Expand All @@ -645,7 +656,8 @@ def test_results_len(get_job_results):
@mock.patch('time.sleep', return_value=None)
def test_timeout(patched_time_sleep, get_job):
qjob = quantum.QuantumJob(
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.RUNNING)
execution_status=quantum.ExecutionStatus(state=quantum.ExecutionStatus.State.RUNNING),
update_time=UPDATE_TIME,
)
get_job.return_value = qjob
job = cg.EngineJob('a', 'b', 'steve', EngineContext(timeout=500))
Expand Down
Loading