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

Check reservation status for a list of processors #140

Merged
merged 10 commits into from
Feb 11, 2021
1 change: 1 addition & 0 deletions recirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
get_processor_id_by_device_name,
get_sampler_by_name,
execute_in_queue,
get_available_processors
)

from recirq.documentation_utils import (
Expand Down
39 changes: 39 additions & 0 deletions recirq/engine_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import math
import os
import uuid
import datetime
from dataclasses import dataclass, field
from typing import List, Any, Optional, Callable, Dict, Union

Expand All @@ -25,6 +26,8 @@
from cirq import work, study, circuits, ops
from cirq.google import devices as cg_devices, gate_sets, engine as cg_engine
from cirq.google.engine.engine_job import TERMINAL_STATES
from cirq.google.engine.client.quantum_v1alpha1.gapic import enums
from cirq.google import EngineTimeSlot


def _get_program_id(program: Any):
Expand Down Expand Up @@ -372,3 +375,39 @@ async def worker():
await queue.join()
for wjob in worker_jobs:
wjob.cancel()


def _get_current_time():
return datetime.datetime.now()


def get_available_processors(processor_names: List[str]):
"""Checks the reservation status of the processors and returns a list of
processors that are available to run on at the present time.
"""
project_id = os.environ['GOOGLE_CLOUD_PROJECT']
engine = cirq.google.get_engine()
available_processors = []
for processor_name in processor_names:
processor_id = get_processor_id_by_device_name(processor_name)
if processor_id is None:
# Skip the check if this is a simulator.
continue
processor = engine.get_processor(processor_id)
for time_slot in processor.get_schedule():
try:
time_slot = EngineTimeSlot.from_proto(time_slot)
# Parsing the end_time of the last time range in the schedule might give throw an error.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this expected? This seems like a problem. I am not sure we should just ignore ValueError blindly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems to be a problem in the EngineTimeSlot.from_proto function in cirq. For example, the end time of the last interval in the schedule has

end_time {                                                                                                                                  
  seconds: 253402300799
}

which raises ValueError: year 0 is out of range when trying to parse it with the datetime library.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, I will go and fix this in cirq. Let's add a TODO (and ideally, a github issue) to go back and fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO, and created an issue in cirq: quantumlib/Cirq#3787

except ValueError:
continue
# Ignore time slots that do not contain the current time.
if time_slot.start_time < _get_current_time() < time_slot.end_time:
# Time slots need to be either in OPEN_SWIM or reserved by the
# current project to be considered available.
if time_slot.slot_type == enums.QuantumTimeSlot.TimeSlotType.OPEN_SWIM:
available_processors.append(processor_name)
break
if time_slot.slot_type == enums.QuantumTimeSlot.TimeSlotType.RESERVATION and time_slot.project_id == project_id:
available_processors.append(processor_name)
break
return available_processors
40 changes: 40 additions & 0 deletions recirq/engine_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@

import os
import uuid
from datetime import datetime

import pytest
from unittest.mock import patch

from google.protobuf.timestamp_pb2 import Timestamp
import cirq
from cirq.google.engine import EngineTimeSlot
from cirq.google.engine.client.quantum_v1alpha1.gapic import enums
from cirq.google.engine.client.quantum_v1alpha1 import types as qtypes
import recirq
from recirq.engine_utils import _get_program_id



def test_get_program_id():
circuit = cirq.Circuit(cirq.H(cirq.LineQubit(0)))
prog_id = _get_program_id(circuit)
Expand Down Expand Up @@ -108,3 +115,36 @@ def test_sampler_by_name():
assert isinstance(recirq.get_sampler_by_name('Syc23-zeros',
gateset='sqrt-iswap'),
recirq.ZerosSampler)


@patch('cirq.google.engine.engine_client.quantum.QuantumEngineServiceClient')
@patch('recirq.engine_utils._get_current_time')
@patch('cirq.google.engine.EngineProcessor.get_schedule')
def test_get_available_processors_open_swim_1(schedule_mock, time_mock, engine_mock):
os.environ['GOOGLE_CLOUD_PROJECT'] = 'some_project'
schedule_mock.return_value = [
qtypes.QuantumTimeSlot(
processor_name='Sycamore23',
start_time=Timestamp(seconds=100),
end_time=Timestamp(seconds=500),
slot_type=enums.QuantumTimeSlot.TimeSlotType.OPEN_SWIM)
]
time_mock.return_value = datetime.fromtimestamp(300)
assert 'Sycamore23' in recirq.get_available_processors(['Sycamore23'])


@patch('cirq.google.engine.engine_client.quantum.QuantumEngineServiceClient')
@patch('recirq.engine_utils._get_current_time')
@patch('cirq.google.engine.EngineProcessor.get_schedule')
def test_get_available_processors_open_swim_2(schedule_mock, time_mock, engine_mock):
os.environ['GOOGLE_CLOUD_PROJECT'] = 'some_project'
schedule_mock.return_value = [
qtypes.QuantumTimeSlot(
processor_name='Sycamore23',
start_time=Timestamp(seconds=300),
end_time=Timestamp(seconds=500),
slot_type=enums.QuantumTimeSlot.TimeSlotType.OPEN_SWIM)
]
time_mock.return_value = datetime.fromtimestamp(700)
assert recirq.get_available_processors(['Sycamore23']) == []

13 changes: 11 additions & 2 deletions recirq/quantum_chess/experiments/batch_moves.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,17 @@ def apply_moves(b: ab.AsciiBoard, moves: List[str]) -> bool:
def main_loop(args):
f = open(args.filename, 'r')
moves = [line.strip() for line in f]
board = create_board(processor_name=args.processor_name,
if args.processor_name:
processor_name = args.processor_name
else:
# Execute on a quantum processor if it is available.
available_processors = utils.get_available_processors(utils.QUANTUM_PROCESSORS.keys())
if available_processors:
processor_name = available_processors[0]
else:
processor_name = 'Syc54-noiseless'
print(f'Using processor {processor_name}')
board = create_board(processor_name=processor_name,
noise_mitigation=0.1)
b = ab.AsciiBoard(board=board)
if args.position:
Expand All @@ -72,7 +82,6 @@ def parse():
help='path to file that contains one move per line')
parser.add_argument('--processor_name',
type=str,
default='Syc54-noiseless',
help='name of the QuantumProcessor object to use')
parser.add_argument('--position',
type=str,
Expand Down