Beispiel #1
0
    def qpu_compiler_client(
            self) -> Optional[Union[Client, "HTTPCompilerClient"]]:
        if not self._qpu_compiler_client:
            endpoint: Optional[str] = None
            if self.qpu_compiler_endpoint:
                endpoint = self.qpu_compiler_endpoint
            elif self.session:
                endpoint = self.session.config.qpu_compiler_url

            if endpoint is not None:
                if endpoint.startswith(("http://", "https://")):
                    assert isinstance(self._device,
                                      Device) and self.session is not None
                    device_endpoint = urljoin(
                        endpoint,
                        f'devices/{self._device._raw["device_name"]}/')
                    self._qpu_compiler_client = HTTPCompilerClient(
                        endpoint=device_endpoint, session=self.session)
                elif endpoint.startswith("tcp://"):
                    self._qpu_compiler_client = Client(endpoint,
                                                       timeout=self.timeout)
                else:
                    raise UserMessageError(
                        "Invalid endpoint provided to QPUCompiler. Expected protocol in [http://, "
                        f"https://, tcp://], but received endpoint {endpoint}")

        assert (isinstance(self._qpu_compiler_client,
                           (Client, HTTPCompilerClient))
                or self._qpu_compiler_client is None)
        return self._qpu_compiler_client
Beispiel #2
0
    def native_quil_to_executable(
            self, nq_program: Program) -> Optional[BinaryExecutableResponse]:
        if not self.qpu_compiler_client:
            raise UserMessageError(
                "It looks like you're trying to compile to an executable, but "
                "do not have access to the QPU compiler endpoint. Make sure you "
                "are engaged to the QPU before trying to do this.")

        self._connect_qpu_compiler()

        if nq_program.native_quil_metadata is None:
            warnings.warn(
                "It looks like you're trying to call `native_quil_to_binary` on a "
                "Program that hasn't been compiled via `quil_to_native_quil`. This is "
                "ok if you've hand-compiled your program to our native gateset, "
                "but be careful!")

        arithmetic_response = rewrite_arithmetic(nq_program)

        request = BinaryExecutableRequest(quil=arithmetic_response.quil,
                                          num_shots=nq_program.num_shots)
        response: BinaryExecutableResponse = cast(
            BinaryExecutableResponse,
            self.qpu_compiler_client.call("native_quil_to_binary", request),
        )

        # hack! we're storing a little extra info in the executable binary that we don't want to
        # expose to anyone outside of our own private lives: not the user, not the Forest server,
        # not anyone.
        response.recalculation_table = arithmetic_response.recalculation_table  # type: ignore
        response.memory_descriptors = _collect_memory_descriptors(nq_program)
        response.ro_sources = _collect_classical_memory_write_locations(
            nq_program)
        return response
Beispiel #3
0
    def get_calibration_program(self) -> Program:
        """
        Get the Quil-T calibration program associated with the underlying QPU.

        A calibration program contains a number of DEFCAL, DEFWAVEFORM, and
        DEFFRAME instructions. In sum, those instructions describe how a Quil-T
        program should be translated into analog instructions for execution on
        hardware.

        :returns: A Program object containing the calibration definitions."""
        self._connect_qpu_compiler()
        request = QuiltCalibrationsRequest(target_device=self.target_device)
        if not self.qpu_compiler_client:
            raise UserMessageError(
                # TODO Improve this error message with a reference to
                # the pyquil config docs?
                "It looks like you're trying to request Quil-T calibrations, but "
                "do not have access to the QPU compiler endpoint. Make sure you "
                "are engaged to the QPU or have configured qpu_compiler_endpoint "
                "in your pyquil configuration.")
        response = cast(
            QuiltCalibrationsResponse,
            self.qpu_compiler_client.call("get_quilt_calibrations", request),
        )
        calibration_program = parse_program(response.quilt)
        return calibration_program
Beispiel #4
0
 def assert_valid_auth_credential(self) -> None:
     """
     assert_valid_auth_credential will check to make sure the user has a valid
     auth credential configured. This assertion is made lazily - it is called
     only after the user has received a 401 or 403 from forest server. See
     _base_connection.py::ForestSession#_refresh_auth_token.
     """
     if self.user_auth_token is None and self.qmi_auth_token is None:
         raise UserMessageError(
             f"Your configuration does not have valid authentication credentials."
             f"Please visit {self.qcs_url}/auth/token to download credentials"
             f"and save to {self.user_auth_token_path}.")
Beispiel #5
0
    def call(self,
             method: str,
             payload: Optional[Message] = None,
             *,
             rpc_timeout: int = 30) -> Message:
        """
        A partially-compatible implementation of rpcq.Client#call, which allows calling rpcq
        methods over HTTP following the scheme:

            POST <endpoint>/<method>
            body: json-serialized <payload>

        This implementation is meant for use only with the QPUCompiler and is not intended to be
        a fully-compatible port of rpcq from ZeroMQ to HTTP.

        If the request succeeds (per HTTP response code), the body of the response is parsed into
        an RPCQ Message.

        If the request fails, the response body should be a JSON object with a ``message`` field
        indicating the cause of the failure. If present, that message is delivered to the user.

        :param payload: The rpcq message body.
        :param rpc_timeout: The number of seconds to wait to read data back from the service
            after connection.
            @see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
        """
        url = urljoin(self.endpoint, method)

        if payload:
            body = to_json(payload)  # type: ignore
        else:
            body = None

        response = self.session.post(url, json=body, timeout=(1, rpc_timeout))

        try:
            response.raise_for_status()
        except RequestException as e:
            message = f"QPU Compiler {method} failed: "

            try:
                contents = response.json()
            except Exception:
                contents = None

            if contents and contents.get("message"):
                message += contents.get("message")
            else:
                message += "please try again or contact support."

            raise UserMessageError(message) from e

        return cast(Message, from_json(response.text))  # type: ignore
Beispiel #6
0
 def qpu_compiler_client(self) -> Client:
     if not self._qpu_compiler_client:
         endpoint = self.qpu_compiler_endpoint or self.session.config.qpu_compiler_url
         if endpoint is not None:
             if endpoint.startswith(("http://", "https://")):
                 device_endpoint = urljoin(
                     endpoint,
                     f'devices/{self._device._raw["device_name"]}/')
                 self._qpu_compiler_client = HTTPCompilerClient(
                     endpoint=device_endpoint, session=self.session)
             elif endpoint.startswith("tcp://"):
                 self._qpu_compiler_client = Client(endpoint,
                                                    timeout=self.timeout)
             else:
                 raise UserMessageError(
                     "Invalid endpoint provided to QPUCompiler. Expected protocol in [http://, "
                     f"https://, tcp://], but received endpoint {endpoint}")
     return self._qpu_compiler_client
Beispiel #7
0
    def native_quil_to_executable(
            self,
            nq_program: Program,
            *,
            debug: bool = False) -> Optional[QuiltBinaryExecutableResponse]:
        if not self.qpu_compiler_client:
            raise UserMessageError(
                "It looks like you're trying to compile to an executable, but "
                "do not have access to the QPU compiler endpoint. Make sure you "
                "are engaged to the QPU before trying to do this.")

        self._connect_qpu_compiler()

        arithmetic_response = rewrite_arithmetic(nq_program)

        request = QuiltBinaryExecutableRequest(quilt=arithmetic_response.quil,
                                               num_shots=nq_program.num_shots)
        response = cast(
            QuiltBinaryExecutableResponse,
            self.qpu_compiler_client.call("native_quilt_to_binary",
                                          request,
                                          rpc_timeout=self.timeout),
        )

        response.recalculation_table = arithmetic_response.recalculation_table  # type: ignore
        response.memory_descriptors = _collect_memory_descriptors(nq_program)

        # Convert strings to MemoryReference for downstream processing.
        response.ro_sources = [(parse_mref(mref), source)
                               for mref, source in response.ro_sources]

        # TODO (kalzoo): this is a temporary workaround to migrate memory location parsing from
        # the client side (where it was pre-quilt) to the service side. In some cases, the service
        # won't return ro_sources, and so we can fall back to parsing the change on the client side.
        if response.ro_sources == []:
            response.ro_sources = _collect_classical_memory_write_locations(
                nq_program)

        if not debug:
            response.debug = {}

        return response
Beispiel #8
0
    def _build_client(self) -> Client:
        endpoint: Optional[str] = None
        if self.endpoint:
            endpoint = self.endpoint
        elif self.session and self.session.config.qpu_url:
            endpoint = self.session.config.qpu_url

        if endpoint is None:
            raise UserMessageError(
                """It looks like you've tried to run a program against a QPU but do
not currently have a reservation on one. To reserve time on Rigetti
QPUs, use the command line interface, qcs, which comes pre-installed
in your QMI. From within your QMI, type:

    qcs reserve --lattice <lattice-name>

For more information, please see the docs at
https://www.rigetti.com/qcs/docs/reservations or reach out to Rigetti
support at [email protected].""")

        logger.debug("QPU Client connecting to %s", endpoint)

        return Client(endpoint, auth_config=self._get_client_auth_config())
Beispiel #9
0
    def _engage(self) -> Optional["Engagement"]:
        """
        The heart of the QPU authorization process, ``engage`` makes a request to
        the dispatch server for the information needed to communicate with the QPU.

        This is a standard GraphQL request, authenticated using the access token
        retrieved from Forest Server.

        The response includes the endpoints to the QPU and QPU Compiler Server,
        along with the set of keys necessary to connect to the QPU and the time at
        which that key set expires.
        """
        query = """
          mutation Engage($name: String!) {
            engage(input: { lattice: { name: $name }}) {
              success
              message
              engagement {
                type
                qpu {
                    endpoint
                    credentials {
                        clientPublic
                        clientSecret
                        serverPublic
                    }
                }
                compiler {
                    endpoint
                }
                expiresAt
              }
            }
          }
        """
        if not self.lattice_name:
            logger.debug(
                "ForestSession requires lattice_name in order to engage")
            return None

        logger.debug("Requesting engagement from %s", self.config.dispatch_url)
        variables = dict(name=self.lattice_name)
        query_response = self._request_graphql_retry(self.config.dispatch_url,
                                                     query=query,
                                                     variables=variables)

        if query_response.get("errors"):
            errors = query_response.get("errors", [])
            error_messages = map(lambda error: error["message"],
                                 errors)  # type: ignore
            raise UserMessageError(
                f"Failed to engage: {','.join(error_messages)}")

        engagement_response = query_response.get("data",
                                                 {}).get("engage", None)
        if engagement_response and engagement_response.get("success") is True:
            logger.debug("Engagement successful")
            engagement_data = engagement_response.get("engagement", {})
            return Engagement(
                client_secret_key=engagement_data.get("qpu", {}).get(
                    "credentials", {}).get("clientSecret", "").encode("utf-8"),
                client_public_key=engagement_data.get("qpu", {}).get(
                    "credentials", {}).get("clientPublic", "").encode("utf-8"),
                server_public_key=engagement_data.get("qpu", {}).get(
                    "credentials", {}).get("serverPublic", "").encode("utf-8"),
                expires_at=engagement_data.get("expiresAt", {}),
                qpu_endpoint=engagement_data.get("qpu", {}).get("endpoint"),
                qpu_compiler_endpoint=engagement_data.get("compiler",
                                                          {}).get("endpoint"),
            )
        else:
            raise UserMessageError(
                f"Unable to engage {self.lattice_name}: "
                f"{engagement_response.get('message', 'No message')}")