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

Simulated Engine Implementation #4638

Merged
merged 21 commits into from
Nov 29, 2021

Conversation

dstrain115
Copy link
Collaborator

Add AbstractEngine Interface

This PR adds three groups of classes to cirq_google.

The first four classes define abstract base classes (ABC)
for the four main types of the Engine:

AbstractJob, AbstractProgram, AbstractProcessor, AbstractEngine

These classes closely model the currect EngineJob, EngineProgram,
EngineProcessor, and Engine classes with the methods turned into
abstract models.

The second group is a partially implemented sub-classes that define
in-memory objects for functionality unlikely to be implemented
differently, such as reservations, labels, and descriptions:

AbstractLocalJob, AbstractLocalProgram,
AbstractLocalProcessor, AbstractLocalEngine

Lastly, the final group of classes defines implementation for
an Engine emulator backed by a sampler, such as the cirq simulator
or other local simulator.

SimulatedLocalJob, SimulatedLocalProgram,
SimulatedLocalProcessor, SimulatedLocalEngine

- Wrapper around sampler to do device related validation.
- This will be used in AbstractEngine in order to centralize
and simplify validation.
This PR adds three groups of classes to cirq_google.

The first four classes define abstract base classes (ABC)
for the four main types of the Engine:

AbstractJob, AbstractProgram, AbstractProcessor, AbstractEngine

These classes closely model the currect EngineJob, EngineProgram,
EngineProcessor, and Engine classes with the methods turned into
abstract models.

The second group is a partially implemented sub-classes that define
in-memory objects for functionality unlikely to be implemented
differently, such as reservations, labels, and descriptions:

AbstractLocalJob, AbstractLocalProgram,
AbstractLocalProcessor, AbstractLocalEngine

Lastly, the final group of classes defines implementation for
an Engine emulator backed by a sampler, such as the cirq simulator
or other local simulator.

SimulatedLocalJob, SimulatedLocalProgram,
SimulatedLocalProcessor, SimulatedLocalEngine
@dstrain115 dstrain115 requested review from cduck, vtomole, wcourtney and a team as code owners November 8, 2021 21:26
@dstrain115 dstrain115 requested a review from viathor November 8, 2021 21:26
@google-cla google-cla bot added the cla: yes Makes googlebot stop complaining. label Nov 8, 2021
@CirqBot CirqBot added the size: XL lines changed >1000 label Nov 8, 2021
@dstrain115 dstrain115 requested a review from mpharrigan November 8, 2021 21:37
Copy link
Collaborator

@MichaelBroughton MichaelBroughton left a comment

Choose a reason for hiding this comment

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

Can we break this up into smaller pieces ?

@MichaelBroughton MichaelBroughton self-assigned this Nov 9, 2021
@dstrain115 dstrain115 changed the title Engine interface Entire engine interface (draft) Nov 9, 2021
@dstrain115 dstrain115 changed the title Entire engine interface (draft) Simulated Engine Implementation Nov 9, 2021
@dstrain115
Copy link
Collaborator Author

I merged with master, which brought file count down from 25 to 20.

I have split this into 3 PRs:

  1. Abstract interface only, no functionality (4 files) Add Abstract Engine Interface #4644
  2. Partial local implementation (8 additional files) Add Local implementation of abstract engine classes #4645
  3. Full implementation using simulated sampler (8 additional files) Simulated Engine Implementation #4638 (This PR)

Once the first two PRs are merged, this will be down to 8 files.

@dstrain115
Copy link
Collaborator Author

Ok, the first two PRs are in and merged. This PR is ready to review with only 8 files now!

Copy link
Collaborator

@95-martin-orion 95-martin-orion left a comment

Choose a reason for hiding this comment

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

First pass - I still need to do a deeper dive on simulated_local_processor[_test].py.

from cirq_google.engine.abstract_program import AbstractProgram
from cirq_google.engine.simulated_local_engine import SimulatedLocalEngine
from cirq_google.engine.simulated_local_processor import SimulatedLocalProcessor

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm surprised there's so much in this test. Can we limit if to behavior above and beyond what's covered in the parent class tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am loathe to remove these tests. Even if they are redundant, it is nice to test that all the cross-references work with the final child class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

SGTM.


This function will fail if any other type of results were returned.
"""
raise NotImplemented # coverage: ignore
Copy link
Collaborator

Choose a reason for hiding this comment

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

Recommend adding a simple test to assert that this is not implemented.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

API or for testing or mocking.

Attributes:
sammpler: A `cirq.Sampler` that can execute the quantum jobs.
Copy link
Collaborator

Choose a reason for hiding this comment

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

sammpler -> sampler

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

"""A processor backed by a sampler and device.

Intended for local simulation testing, this processor will
create a VAlidationSampler that will validate requests based on
Copy link
Collaborator

Choose a reason for hiding this comment

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

VAlidation -> Validation

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 106 to 114
latest = None
current_calibration = None
for calibration_seconds in self._calibrations:
if calibration_seconds <= timestamp and (
latest is None or latest < calibration_seconds
):
latest = calibration_seconds
current_calibration = self._calibrations[latest]
return current_calibration
Copy link
Collaborator

Choose a reason for hiding this comment

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

[Optional] There's probably a more concise/pythonic way of doing this with max(..., key=...). Leaving it up to you whether to switch to that form.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea. I didn't realize you could use a key with max. Good trick.

return self._device

def get_device_specification(self) -> Optional[v2.device_pb2.DeviceSpecification]:
raise NotImplemented # coverage: ignore
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add negative test for this method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return job

def run_calibration(self, *args, **kwargs):
raise NotImplemented # coverage: ignore
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add negative test

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

from cirq_google.engine.simulated_local_processor import SimulatedLocalProcessor


class SimulatedLocalProgram(AbstractLocalProgram):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Tests for this class? Kind of surprised coverage check isn't firing for this...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added some basic tests. Coverage was previously provided by tests in processor and engine.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Where are these tests? There's no simulated_local_program_test.py in this PR - did it get missed in the commit?

This class functions as a parent class for a `SimulatedLocalJob`
object.

This is an abstract class that inheritors should implement.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this line? This doesn't look like an abstract class, at least.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

@MichaelBroughton MichaelBroughton left a comment

Choose a reason for hiding this comment

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

Just a few comments from me and then I think we should be good to merge.

def __init__(
self,
*args,
sampler: cirq.Sampler = cirq.Simulator(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: unsafe default.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

program=program, params=sweeps[0], repetitions=reps
)
else:
return [self._sampler.run(program=program, repetitions=reps)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the default on run_sweep here not have the same return signature as the one above ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am not sure I understand the question. This line uses run() which returns a single Result. The line above uses run_sweep which returns a List[Result].

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, let me ask instead: Why couldn't we do something like:

self._state = quantum.enums.thing
return self._sampler.run_sweep(program=program, sweeps[0] if sweeps else None, repetitions=reps)

and get rid of the need for the if/else block ?

Comment on lines 142 to 153
for calibration_seconds in self._calibrations:
if (
earliest_timestamp_seconds is not None
and earliest_timestamp_seconds > calibration_seconds
):
continue
if (
latest_timestamp_seconds is not None
and latest_timestamp_seconds < calibration_seconds
):
continue
calibration_list.append(self._calibrations[calibration_seconds])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: can this be tightened up using list comprehensions instead of the these guarding continues ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 169 to 178
for program in self._programs.values():
if created_before is not None and created_before < program.create_time():
continue
if created_after is not None and created_after > program.create_time():
continue
if has_labels is not None:
labels = program.labels()
if any(key not in labels or labels[key] != has_labels[key] for key in has_labels):
continue
programs.append(program)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: can probably just use list comprehension here too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

@95-martin-orion 95-martin-orion left a comment

Choose a reason for hiding this comment

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

Finished simulated_local_processor_test.py review, just a few additional items.

# 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.
"""A helper for jobs that have been created on the Quantum Engine."""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Update file docstring? Same for simulated_local_job[_test].py.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


VALID_LANGUAGES = [
'type.googleapis.com/cirq.google.api.v2.Program',
'type.googleapis.com/cirq.google.api.v2.FocusedCalibration',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since run_calibration isn't implemented, should we remove FocusedCalibration from this list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea. Removed.

assert np.all(results[1].measurements['m'] == 0)
assert job.execution_status() == quantum.enums.ExecutionStatus.State.SUCCESS

# iteration
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing test block? Otherwise remove comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed, was tested above.

Copy link
Collaborator

@MichaelBroughton MichaelBroughton left a comment

Choose a reason for hiding this comment

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

LGTM, left two final comments, feel free to ignore if you want.

program=program, params=sweeps[0], repetitions=reps
)
else:
return [self._sampler.run(program=program, repetitions=reps)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, let me ask instead: Why couldn't we do something like:

self._state = quantum.enums.thing
return self._sampler.run_sweep(program=program, sweeps[0] if sweeps else None, repetitions=reps)

and get rid of the need for the if/else block ?

Comment on lines 158 to 167
return list(
filter(
lambda program: after_limit < program.create_time() < before_limit
and all(
(key in program.labels() and program.labels()[key] == labels[key])
for key in labels
),
self._programs.values(),
)
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps storing an intermediate list or two here to break up the big compound expression might be a little easier to parse as a reader ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Made an intermediate function instead.

Copy link
Collaborator

@95-martin-orion 95-martin-orion left a comment

Choose a reason for hiding this comment

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

LGTM once simulated_local_program_test comment is resolved.

@dstrain115 dstrain115 merged commit 973b2c7 into quantumlib:master Nov 29, 2021
rht pushed a commit to rht/Cirq that referenced this pull request May 1, 2023
This PR adds a final group of classes to abstract engine to define implementation for
an Engine emulator backed by a sampler, such as the cirq simulatoror other local simulator.

SimulatedLocalJob, SimulatedLocalProgram, SimulatedLocalProcessor, SimulatedLocalEngine

This will allow a user to create relevant Engine objects backed by a sampler and validator of their choice (such as a simulator based on a device).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Makes googlebot stop complaining. size: XL lines changed >1000
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants