-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Conversation
- 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
There was a problem hiding this 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 ?
I merged with master, which brought file count down from 25 to 20. I have split this into 3 PRs:
Once the first two PRs are merged, this will be down to 8 files. |
… into engine_interface
Ok, the first two PRs are in and merged. This PR is ready to review with only 8 files now! |
There was a problem hiding this 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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sammpler
-> sampler
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VAlidation
-> Validation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add negative test
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this 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(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: unsafe default.
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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].
There was a problem hiding this comment.
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 ?
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]) |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this 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.""" |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed, was tested above.
There was a problem hiding this 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)] |
There was a problem hiding this comment.
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 ?
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(), | ||
) | ||
) |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
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).
Add AbstractEngine Interface