def test_forest_session_request_authenticated_with_qmi_auth(): config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "qmi_auth_token_path", api_fixture_path("qmi_auth_token_valid.json")) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_invalid.json")) config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "url", "mock://forest") config._parse_auth_tokens() session = ForestSession(config=config) mock_adapter = requests_mock.Adapter() session.mount("mock", mock_adapter) url = "%s/devices" % config.forest_url headers = { # access token from ./data/qmi_auth_token_valid.json. "X-QMI-AUTH-TOKEN": "secret" } mock_adapter.register_uri("GET", url, status_code=200, json=[{ "id": 0 }], headers=headers) devices = session.get(url).json() assert len(devices) == 1 assert devices[0]["id"] == 0
def test_forest_session_request_engagement(): """ The QPU Endpoint address provided by engagement should be available to the PyQuilConfig object. """ config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["FOREST_CONFIG"].remove_section("Rigetti Forest") config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_invalid.json") ) config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "url", "mock://forest") config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "dispatch_url", "mock://dispatch") config._parse_auth_tokens() session = ForestSession(config=config, lattice_name="fake-lattice") mock_adapter = requests_mock.Adapter() session.mount("mock", mock_adapter) url = config.dispatch_url response_list = [ # access token from ./data/user_auth_token_valid.json. {"status_code": 200, "json": {"data": SUCCESSFUL_ENGAGEMENT_RESPONSE}} ] mock_adapter.register_uri("POST", url, response_list=response_list) assert ( config.qpu_url == SUCCESSFUL_ENGAGEMENT_RESPONSE["engage"]["engagement"]["qpu"]["endpoint"] ) assert ( config.qpu_compiler_url == SUCCESSFUL_ENGAGEMENT_RESPONSE["engage"]["engagement"]["compiler"]["endpoint"] )
def test_forest_session_engagement_not_requested_if_config_present(): """ Engagement is the source-of-last-resort for configuration data. If all endpoints are provided elsewhere, then engagement should never be requested. """ config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_invalid.json")) config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "url", "mock://forest") config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "dispatch_url", "mock://dispatch") config._parse_auth_tokens() session = ForestSession(config=config, lattice_name="fake-lattice") mock_adapter = requests_mock.Adapter() session.mount("mock", mock_adapter) url = config.dispatch_url response_list = [ # access token from ./data/user_auth_token_valid.json. { "status_code": 200, "json": { "data": SUCCESSFUL_ENGAGEMENT_RESPONSE } } ] mock_adapter.register_uri("POST", url, response_list=response_list) assert len(mock_adapter.request_history) == 0 assert config.qpu_url == config.config_parsers["FOREST_CONFIG"].get( "Rigetti Forest", "qpu_endpoint_address")
def test_forest_session_request_engagement_failure(): """ If engagement fails, no QPU URL is available to the client. """ config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["FOREST_CONFIG"].remove_section("Rigetti Forest") config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_invalid.json")) config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "url", "mock://forest") config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "dispatch_url", "mock://dispatch") config._parse_auth_tokens() session = ForestSession(config=config, lattice_name="fake-lattice") mock_adapter = requests_mock.Adapter() session.mount("mock", mock_adapter) url = config.dispatch_url response_list = [ # access token from ./data/user_auth_token_valid.json. { "status_code": 200, "json": { "data": FAILED_ENGAGEMENT_RESPONSE } } ] mock_adapter.register_uri("POST", url, response_list=response_list)
def test_forest_session_request_refresh_user_auth_token(): config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "qmi_auth_token_path", api_fixture_path("qmi_auth_token_invalid.json") ) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_valid.json") ) config.config_parsers["QCS_CONFIG"].set("Rigetti Forest", "url", "mock://forest") config._parse_auth_tokens() session = ForestSession(config=config) mock_adapter = requests_mock.Adapter() session.mount("mock", mock_adapter) url = "%s/devices" % config.forest_url response_list = [ # access token from ./data/user_auth_token_valid.json. { "status_code": 401, "json": {"error": "user_unauthorized"}, "headers": {"Authorization": "Bearer secret"}, }, # access token from new_user_auth_token. {"status_code": 200, "json": [{"id": 0}], "headers": {"Authorization": "Bearer secret2"}}, ] mock_adapter.register_uri("GET", url, response_list=response_list) refresh_url = "%s/auth/idp/oauth2/v1/token" % config.forest_url def refresh_matcher(request): body = dict(urllib.parse.parse_qsl(request.text)) return (body["refresh_token"] == "supersecret") and (body["grant_type"] == "refresh_token") new_user_auth_token = { "access_token": "secret2", "refresh_token": "supersecret2", "scope": "openid offline_access profile", } mock_adapter.register_uri( "POST", refresh_url, status_code=200, json=new_user_auth_token, additional_matcher=refresh_matcher, ) # refresh will write the new auth tokens to file. Do not over-write text fixture data. config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "qmi_auth_token_path", "/tmp/qmi_auth_token_invalid.json" ) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", "/tmp/user_auth_token_valid.json" ) devices = session.get(url).json() assert len(devices) == 1 assert devices[0]["id"] == 0
def test_config_qcs_auth_headers_valid_qmi_token(): config = PyquilConfig(TEST_CONFIG_PATHS) config.user_auth_token = None config.qmi_auth_token = { "access_token": "secret", "refresh_token": "supersecret" } assert "Authorization" not in config.qcs_auth_headers assert "X-QMI-AUTH-TOKEN" in config.qcs_auth_headers assert config.qcs_auth_headers["X-QMI-AUTH-TOKEN"] == "secret"
def test_config_qcs_auth_headers_valid_user_token(): config = PyquilConfig(TEST_CONFIG_PATHS) config.user_auth_token = { "access_token": "secret", "refresh_token": "supersecret", "scope": "openid profile", } config.qmi_auth_token = None assert "Authorization" in config.qcs_auth_headers assert "X-QMI-AUTH-TOKEN" not in config.qcs_auth_headers assert config.qcs_auth_headers["Authorization"] == "Bearer secret"
def test_qpu_run(): config = PyquilConfig() if config.qpu_url and config.qpu_compiler_url: g = nx.Graph() g.add_node(0) device = NxDevice(g) qc = QuantumComputer( name="pyQuil test QC", qam=QPU(endpoint=config.qpu_url, user="******"), device=device, compiler=QPUCompiler( quilc_endpoint=config.quilc_url, qpu_compiler_endpoint=config.qpu_compiler_url, device=device, ), ) bitstrings = qc.run_and_measure(program=Program(X(0)), trials=1000) assert bitstrings[0].shape == (1000, ) assert np.mean(bitstrings[0]) > 0.8 bitstrings = qc.run(qc.compile(Program(X(0)))) assert bitstrings.shape == (0, 0) else: pytest.skip( "QPU or compiler-server not available; skipping QPU run test.")
def _get_raw_lattice_data(lattice_name: str = None): """ Produces a dictionary of raw data for a lattice as queried from the Forest 2.0 server. Returns a dictionary of the form { "name": the name of the lattice as a string, "device_name": the name of the device, given as a string, that the lattice lies on, "specs": a Specs object, serialized as a dictionary, "isa": an ISA object, serialized as a dictionary, "noise_model": a NoiseModel object, serialized as a dictionary } """ from pyquil.api._base_connection import get_session, get_json from requests.exceptions import MissingSchema session = get_session() config = PyquilConfig() try: res = get_json(session, f"{config.forest_url}/lattices/{lattice_name}") except MissingSchema: raise ValueError( f"Error finding lattice `{lattice_name}` at Forest 2.0 server " f"""endpoint `{config.forest_url}`. Most likely, you're missing an address for the Forest 2.0 server endpoint, or the address is invalid. This can be set through the environment variable FOREST_URL or by changing the following lines in the QCS config file (by default, at ~/.qcs_config): [Rigetti Forest] url = https://rigetti.com/valid/forest/url""") return res["lattice"]
def __init__(self, sync_endpoint=None, compiler_endpoint=None, forest_cloud_endpoint=None): """ Represents a connection to Forest containing methods to wrap all possible API endpoints. Users should not use methods from this class directly. :param sync_endpoint: The endpoint of the server for running QVM jobs :param compiler_endpoint: The endpoint of the server for running quilc compiler jobs :param forest_cloud_endpoint: The endpoint of the forest cloud server """ pyquil_config = PyquilConfig() if sync_endpoint is None: sync_endpoint = pyquil_config.qvm_url if compiler_endpoint is None: compiler_endpoint = pyquil_config.compiler_url if forest_cloud_endpoint is None: forest_cloud_endpoint = pyquil_config.forest_url self.sync_endpoint = sync_endpoint self.compiler_endpoint = compiler_endpoint self.forest_cloud_endpoint = forest_cloud_endpoint self.session = get_session()
def test_local_conjugate_request(benchmarker): config = PyquilConfig() if config.compiler_url is not None: cxn = BenchmarkConnection(endpoint=config.compiler_url) response = cxn.apply_clifford_to_pauli(Program("H 0"), PauliTerm("X", 0, 1.0)) assert isinstance(response, PauliTerm) assert str(response) == "(1+0j)*Z0"
def get_session(): """ Create a requests session to access the REST API :return: requests session :rtype: Session """ config = PyquilConfig() session = requests.Session() retry_adapter = HTTPAdapter( max_retries=Retry(total=3, method_whitelist=['POST'], status_forcelist=[502, 503, 504, 521, 523], backoff_factor=0.2, raise_on_status=False)) session.mount("http://", retry_adapter) session.mount("https://", retry_adapter) # We need this to get binary payload for the wavefunction call. session.headers.update({ "Accept": "application/octet-stream", "X-User-Id": config.user_id, "X-Api-Key": config.api_key }) session.headers.update({'Content-Type': 'application/json; charset=utf-8'}) return session
def list_devices(): """ Query the Forest 2.0 server for a list of underlying QPU devices. NOTE: These can't directly be used to manufacture pyQuil Device objects, but this gives a list of legal values that can be supplied to list_lattices to filter its (potentially very noisy) output. :return: A list of device names. """ # For the record, the dictionary stored in "devices" that we're getting back is keyed on device # names and has this structure in its values: # # { # "is_online": a boolean indicating the availability of the device, # "is_retuning": a boolean indicating whether the device is busy retuning, # "specs": a Specs object describing the entire device, serialized as a dictionary, # "isa": an ISA object describing the entire device, serialized as a dictionary, # "noise_model": a NoiseModel object describing the entire device, serialized as a dictionary # } session = get_session() config = PyquilConfig() return sorted( get_json(session, config.forest_url + "/devices")["devices"].keys())
def reset(self): """ Reset the state of the QPUCompiler Client connections. """ pyquil_config = PyquilConfig() self.quilc_client = refresh_client(self.quilc_client, pyquil_config.quilc_url) self.qpu_compiler_client = refresh_client(self.qpu_compiler_client, pyquil_config.qpu_compiler_url)
def compiler(test_device): try: config = PyquilConfig() compiler = LocalQVMCompiler(endpoint=config.compiler_url, device=test_device) compiler.quil_to_native_quil(Program(I(0))) return compiler except (RequestException, UnknownApiError) as e: return pytest.skip("This test requires compiler connection: {}".format(e))
def test_local_rb_sequence(benchmarker): config = PyquilConfig() if config.compiler_url is not None: cxn = BenchmarkConnection(endpoint=config.compiler_url) response = cxn.generate_rb_sequence(2, [PHASE(np.pi / 2, 0), H(0)], seed=52) assert [prog.out() for prog in response] == \ ["H 0\nPHASE(pi/2) 0\nH 0\nPHASE(pi/2) 0\nPHASE(pi/2) 0\n", "H 0\nPHASE(pi/2) 0\nH 0\nPHASE(pi/2) 0\nPHASE(pi/2) 0\n"]
def qpu_compiler(test_device): try: config = PyquilConfig() compiler = QPUCompiler(endpoint=config.compiler_url, device=test_device, timeout=0.5) compiler.quil_to_native_quil(Program(I(0))) return compiler except Exception as e: return pytest.skip(f"This test requires compiler connection: {e}")
def compiler(): try: config = PyquilConfig() device = get_qc("3q-qvm").device compiler = QVMCompiler(endpoint=config.quilc_url, device=device) compiler.quil_to_native_quil(Program(Id(0))) return compiler except (RequestException, UnknownApiError, QVMNotRunning, TypeError) as e: return pytest.skip("This test requires compiler connection: {}".format(e))
def test_http_compilation_failure(compiler): device_name = "test_device" mock_url = "http://mock-qpu-compiler" config = PyquilConfig(TEST_CONFIG_PATHS) session = get_session(config=config) mock_adapter = requests_mock.Adapter() session.mount("http://", mock_adapter) headers = { # access token from ./data/user_auth_token_valid.json. "Authorization": "Bearer secret" } mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/get_version_info", status_code=200, json={}, headers=headers, ) mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/native_quilt_to_binary", status_code=500, json={"message": "test compilation failed"}, headers=headers, ) mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/get_quilt_calibrations", status_code=200, json=CALIBRATIONS_RESPONSE, headers=headers, ) device = Device(name="not_actually_device_name", raw={ "device_name": device_name, "isa": DUMMY_ISA_DICT }) compiler = QPUCompiler( quilc_endpoint=session.config.quilc_url, qpu_compiler_endpoint=mock_url, device=device, session=session, ) native_quil = compiler.quil_to_native_quil(simple_program()) try: compiler.native_quil_to_executable(native_quil) except UserMessageError as e: assert "test compilation failed" in str(e)
def compiler(test_device): try: config = PyquilConfig() compiler = QVMCompiler(endpoint=config.quilc_url, device=test_device, timeout=1) compiler.quil_to_native_quil(Program(I(0))) return compiler except (RequestException, QuilcNotRunning, UnknownApiError, TimeoutError) as e: return pytest.skip("This test requires compiler connection: {}".format(e)) except QuilcVersionMismatch as e: return pytest.skip("This test requires a different version of quilc: {}".format(e))
def test_compile_with_quilt_calibrations(compiler): device_name = "test_device" mock_url = "http://mock-qpu-compiler" config = PyquilConfig(TEST_CONFIG_PATHS) session = get_session(config=config) mock_adapter = requests_mock.Adapter() session.mount("http://", mock_adapter) headers = { # access token from ./data/user_auth_token_valid.json. "Authorization": "Bearer secret" } mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/get_version_info", status_code=200, json={}, headers=headers, ) mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/native_quilt_to_binary", status_code=200, json=SIMPLE_RESPONSE, headers=headers, ) device = Device(name="not_actually_device_name", raw={ "device_name": device_name, "isa": DUMMY_ISA_DICT }) compiler = QPUCompiler( quilc_endpoint=session.config.quilc_url, qpu_compiler_endpoint=mock_url, device=device, session=session, ) program = simple_program() q = FormalArgument("q") defn = DefCalibration( "H", [], [q], [RZ(math.pi / 2, q), RX(math.pi / 2, q), RZ(math.pi / 2, q)]) cals = [defn] program._calibrations = cals # this should more or less pass through compilation_result = compiler.quil_to_native_quil(program, protoquil=True) assert compilation_result.calibrations == cals assert program.calibrations == cals assert compilation_result == program
def test_engagement_not_requested_when_unnecessary(): config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["FOREST_CONFIG"].set("Rigetti Forest", "qpu_compiler_address", "tcp://fake_compiler:5555") config.config_parsers["FOREST_CONFIG"].set("Rigetti Forest", "qpu_endpoint_address", "tcp://fake_qpu:5555") assert config.qpu_compiler_url == "tcp://fake_compiler:5555" assert config.qpu_url == "tcp://fake_qpu:5555" assert config.engagement is None
def __init__(self, sync_endpoint=None, compiler_endpoint=None): """ Represents a connection to Forest containing methods to wrap all possible API endpoints. Users should not use methods from this class directly. :param sync_endpoint: The endpoint of the server for running (small) QVM jobs :param compiler_endpoint: The endpoint of the server for running (small) compiler jobs """ if not sync_endpoint: pyquil_config = PyquilConfig() sync_endpoint = pyquil_config.qvm_url if not compiler_endpoint: pyquil_config = PyquilConfig() compiler_endpoint = pyquil_config.compiler_url self.sync_endpoint = sync_endpoint self.compiler_endpoint = compiler_endpoint self.session = get_session()
def get_active_lattice() -> Optional[str]: """ Try to query which lattice we're engaged to from QCS. Returns the lattice name if available, otherwise None. """ from rpcq import Client from pyquil.api._config import PyquilConfig try: qcc = Client(endpoint=PyquilConfig().qpu_compiler_url, timeout=1) return qcc.call("get_config_info")["lattice_name"] except: return None
def test_config_parsing(): with patch.dict('os.environ', {"FOREST_CONFIG": os.path.join(os.path.dirname(__file__), "data/forest_config.test"), "QCS_CONFIG": os.path.join(os.path.dirname(__file__), "data/qcs_config.test")}): pq_config = PyquilConfig() assert pq_config.forest_url == "http://dummy_forest_url" assert pq_config.api_key == "pyquil_user_key" assert pq_config.user_id == "pyquil_user_token" assert pq_config.engage_cmd == "dummy_command" assert pq_config.qpu_url == "tcp://dummy_qpu_url" assert pq_config.qvm_url == "http://dummy_qvm_url" assert pq_config.compiler_url == "tcp://dummy_compiler_server_url"
def reset(self): """ Reset the state of the underlying QAM, and the QPU Client connection. """ super().reset() def refresh_client(client: Client, new_endpoint: str) -> Client: timeout = client.timeout client.close() return Client(new_endpoint, timeout) pyquil_config = PyquilConfig() self.client = refresh_client(self.client, pyquil_config.qpu_url)
def get_benchmarker(endpoint: str = None, timeout: float = 10): """ Retrieve an instance of the appropriate AbstractBenchmarker subclass for a given endpoint. :param endpoint: Benchmarking sequence server address. Defaults to the setting in the user's pyQuil config. :param timeout: Number of seconds to wait before giving up on a call. :return: Instance of an AbstractBenchmarker subclass, connected to the given endpoint. """ if endpoint is None: config = PyquilConfig() endpoint = config.quilc_url return BenchmarkConnection(endpoint=endpoint, timeout=timeout)
def test_config_assert_valid_auth_credential(): config = PyquilConfig(TEST_CONFIG_PATHS) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "qmi_auth_token_path", api_fixture_path("qmi_auth_token_invalid.json")) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_invalid.json")) config._parse_auth_tokens() assert config.user_auth_token is None assert config.qmi_auth_token is None with pytest.raises(UserMessageError): config.assert_valid_auth_credential() config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "qmi_auth_token_path", api_fixture_path("qmi_auth_token_valid.json")) config.config_parsers["QCS_CONFIG"].set( "Rigetti Forest", "user_auth_token_path", api_fixture_path("user_auth_token_valid.json")) config._parse_auth_tokens() assert config.user_auth_token is not None assert config.qmi_auth_token is not None config.assert_valid_auth_credential()
def list_devices(): """ Query the Forest 2.0 server for its knowledge of QPUs. :return: A dictionary, keyed on device names. Each value is a dictionary of the form { "is_online": a boolean indicating the availability of the device, "is_retuning": a boolean indicating whether the device is busy retuning, "specs": a Specs object describing the entire device, serialized as a dictionary, "isa": an ISA object describing the entire device, serialized as a dictionary, "noise_model": a NoiseModel object describing the entire device, serialized as a dictionary } """ session = get_session() config = PyquilConfig() return get_json(session, config.forest_url + "/devices")["devices"]
def test_http_compilation(compiler): device_name = "test_device" mock_url = "http://mock-qpu-compiler" config = PyquilConfig(TEST_CONFIG_PATHS) session = get_session(config=config) mock_adapter = requests_mock.Adapter() session.mount("http://", mock_adapter) headers = { # access token from ./data/user_auth_token_valid.json. "Authorization": "Bearer secret" } mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/get_version_info", status_code=200, json={}, headers=headers, ) mock_adapter.register_uri( "POST", f"{mock_url}/devices/{device_name}/native_quil_to_binary", status_code=200, json=SIMPLE_RESPONSE, headers=headers, ) device = Device( name="not_actually_device_name", raw={"device_name": device_name, "isa": DUMMY_ISA_DICT} ) compiler = QPUCompiler( quilc_endpoint=session.config.quilc_url, qpu_compiler_endpoint=mock_url, device=device, session=session, ) compilation_result = compiler.native_quil_to_executable( compiler.quil_to_native_quil(simple_program()) ) assert isinstance(compilation_result, BinaryExecutableResponse) assert compilation_result.program == SIMPLE_RESPONSE["program"]