示例#1
0
class QVMCompiler(AbstractCompiler):
    @_record_call
    def __init__(self,
                 endpoint: str,
                 device: AbstractDevice,
                 timeout: float = 10) -> None:
        """
        Client to communicate with the Compiler Server.

        :param endpoint: TCP or IPC endpoint of the Compiler Server
        :param device: PyQuil Device object to use as compilation target
        """

        if not endpoint.startswith("tcp://"):
            raise ValueError(
                f"PyQuil versions >= 2.4 can only talk to quilc "
                f"versions >= 1.4 over network RPCQ.  You've supplied the "
                f"endpoint '{endpoint}', but this doesn't look like a network "
                f"ZeroMQ address, which has the form 'tcp://domain:port'. "
                f"You might try clearing (or correcting) your COMPILER_URL "
                f"environment variable and removing (or correcting) the "
                f"compiler_server_address line from your .forest_config file.")

        self.endpoint = endpoint
        self.client = Client(endpoint, timeout=timeout)
        td = TargetDevice(isa=device.get_isa().to_dict(),
                          specs=None)  # type: ignore
        self.target_device = td

        try:
            self.connect()
        except QuilcNotRunning as e:
            warnings.warn(
                f"{e}. Compilation using quilc will not be available.")

    def connect(self) -> None:
        try:
            version_dict = self.get_version_info()
            check_quilc_version(version_dict)
        except TimeoutError:
            raise QuilcNotRunning(
                f"Request to quilc at {self.client.endpoint} timed out. "
                "This could mean that quilc is not running, is not reachable, or is "
                "responding slowly.")

    def get_version_info(self) -> Dict[str, Any]:
        return cast(Dict[str, Any], self.client.call("get_version_info"))

    @_record_call
    def quil_to_native_quil(self,
                            program: Program,
                            *,
                            protoquil: Optional[bool] = None) -> Program:
        self.connect()
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.client.call("quil_to_native_quil",
                                    request,
                                    protoquil=protoquil).asdict()
        nq_program = parse_program(response["quil"])
        nq_program.native_quil_metadata = response["metadata"]
        nq_program.num_shots = program.num_shots
        return nq_program

    @_record_call
    def native_quil_to_executable(
            self, nq_program: Program) -> PyQuilExecutableResponse:
        return PyQuilExecutableResponse(
            program=nq_program.out(),
            attributes=_extract_attribute_dictionary_from_program(nq_program),
        )

    @_record_call
    def reset(self) -> None:
        """
        Reset the state of the QVMCompiler quilc connection.
        """
        timeout = self.client.timeout
        self.client.close()  # type: ignore
        self.client = Client(self.endpoint, timeout=timeout)
示例#2
0
class BenchmarkConnection(AbstractBenchmarker):
    """
    Represents a connection to a server that generates benchmarking data.
    """
    @_record_call
    def __init__(self, endpoint: str, timeout: Optional[float] = None):
        """
        Client to communicate with the benchmarking data endpoint.

        :param endpoint: TCP or IPC endpoint of the Compiler Server
        """

        self.client = Client(endpoint, timeout=timeout)

    @_record_call
    def apply_clifford_to_pauli(self, clifford: Program,
                                pauli_in: PauliTerm) -> PauliTerm:
        r"""
        Given a circuit that consists only of elements of the Clifford group,
        return its action on a PauliTerm.

        In particular, for Clifford C, and Pauli P, this returns the PauliTerm
        representing CPC^{\dagger}.

        :param clifford: A Program that consists only of Clifford operations.
        :param pauli_in: A PauliTerm to be acted on by clifford via conjugation.
        :return: A PauliTerm corresponding to clifford * pauli_in * clifford^{\dagger}
        """
        # do nothing if `pauli_in` is the identity
        if is_identity(pauli_in):
            return pauli_in

        indices_and_terms = list(zip(*list(pauli_in.operations_as_set())))

        payload = ConjugateByCliffordRequest(
            clifford=clifford.out(),
            pauli=rpcq.messages.PauliTerm(indices=list(indices_and_terms[0]),
                                          symbols=list(indices_and_terms[1])),
        )
        response: ConjugateByCliffordResponse = self.client.call(
            "conjugate_pauli_by_clifford", payload)
        phase_factor, paulis = response.phase, response.pauli

        pauli_out = PauliTerm("I", 0, 1.0j**phase_factor)
        clifford_qubits = clifford.get_qubits()
        pauli_qubits = pauli_in.get_qubits()
        all_qubits = sorted(
            set(cast(List[int],
                     pauli_qubits)).union(set(cast(List[int],
                                                   clifford_qubits))))
        # The returned pauli will have specified its value on all_qubits, sorted by index.
        #  This is maximal set of qubits that can be affected by this conjugation.
        for i, pauli in enumerate(paulis):
            pauli_out = cast(PauliTerm,
                             pauli_out * PauliTerm(pauli, all_qubits[i]))
        return cast(PauliTerm, pauli_out * pauli_in.coefficient)

    @_record_call
    def generate_rb_sequence(
        self,
        depth: int,
        gateset: Sequence[Gate],
        seed: Optional[int] = None,
        interleaver: Optional[Program] = None,
    ) -> List[Program]:
        """
        Construct a randomized benchmarking experiment on the given qubits, decomposing into
        gateset. If interleaver is not provided, the returned sequence will have the form

            C_1 C_2 ... C_(depth-1) C_inv ,

        where each C is a Clifford element drawn from gateset, C_{< depth} are randomly selected,
        and C_inv is selected so that the entire sequence composes to the identity.  If an
        interleaver G (which must be a Clifford, and which will be decomposed into the native
        gateset) is provided, then the sequence instead takes the form

            C_1 G C_2 G ... C_(depth-1) G C_inv .

        The JSON response is a list of lists of indices, or Nones. In the former case, they are the
        index of the gate in the gateset.

        :param depth: The number of Clifford gates to include in the randomized benchmarking
         experiment. This is different than the number of gates in the resulting experiment.
        :param gateset: A list of pyquil gates to decompose the Clifford elements into. These
         must generate the clifford group on the qubits of interest. e.g. for one qubit
         [RZ(np.pi/2), RX(np.pi/2)].
        :param seed: A positive integer used to seed the PRNG.
        :param interleaver: A Program object that encodes a Clifford element.
        :return: A list of pyquil programs. Each pyquil program is a circuit that represents an
         element of the Clifford group. When these programs are composed, the resulting Program
         will be the randomized benchmarking experiment of the desired depth. e.g. if the return
         programs are called cliffords then `sum(cliffords, Program())` will give the randomized
         benchmarking experiment, which will compose to the identity program.
        """

        # Support QubitPlaceholders: we temporarily index to arbitrary integers.
        # `generate_rb_sequence` handles mapping back to the original gateset gates.
        gateset_as_program = address_qubits(sum(gateset, Program()))
        qubits = len(gateset_as_program.get_qubits())
        gateset_for_api = gateset_as_program.out().splitlines()
        interleaver_out: Optional[str] = None
        if interleaver:
            assert isinstance(interleaver, Program)
            interleaver_out = interleaver.out()

        depth = int(depth)  # needs to be jsonable, no np.int64 please!

        payload = RandomizedBenchmarkingRequest(
            depth=depth,
            qubits=qubits,
            gateset=gateset_for_api,
            seed=seed,
            interleaver=interleaver_out,
        )
        response = self.client.call(
            "generate_rb_sequence",
            payload)  # type: RandomizedBenchmarkingResponse

        programs = []
        for clifford in response.sequence:
            clifford_program = Program()
            # Like below, we reversed the order because the API currently hands back the Clifford
            # decomposition right-to-left.
            for index in reversed(clifford):
                clifford_program.inst(gateset[index])
            programs.append(clifford_program)
        # The programs are returned in "textbook style" right-to-left order. To compose them into
        #  the correct pyquil program, we reverse the order.
        return list(reversed(programs))
示例#3
0
class QPUCompiler(AbstractCompiler):
    @_record_call
    def __init__(
        self,
        quilc_endpoint: Optional[str],
        qpu_compiler_endpoint: Optional[str],
        device: AbstractDevice,
        timeout: int = 10,
        name: Optional[str] = None,
        *,
        session: Optional[ForestSession] = None,
    ) -> None:
        """
        Client to communicate with the Compiler Server.

        :param quilc_endpoint: TCP or IPC endpoint of the Quil Compiler (quilc).
        :param qpu_compiler_endpoint: TCP or IPC endpoint of the QPU Compiler.
        :param device: PyQuil Device object to use as compilation target.
        :param timeout: Number of seconds to wait for a response from the client.
        :param name: Name of the lattice being targeted.
        :param session: ForestSession object, which manages engagement and configuration.
        """

        if not (session or (quilc_endpoint and qpu_compiler_endpoint)):
            raise ValueError(
                "QPUCompiler requires either `session` or both of `quilc_endpoint` and "
                "`qpu_compiler_endpoint`.")

        self.session = session
        self.timeout = timeout

        if quilc_endpoint:
            _quilc_endpoint = quilc_endpoint
        elif self.session and self.session.config.quilc_url:
            _quilc_endpoint = self.session.config.quilc_url
        else:
            raise ValueError("Must provide a 'quilc_endpoint' or a 'session'")

        if not _quilc_endpoint.startswith("tcp://"):
            raise ValueError(
                f"PyQuil versions >= 2.4 can only talk to quilc "
                f"versions >= 1.4 over network RPCQ.  You've supplied the "
                f"endpoint '{quilc_endpoint}', but this doesn't look like a network "
                f"ZeroMQ address, which has the form 'tcp://domain:port'. "
                f"You might try clearing (or correcting) your COMPILER_URL "
                f"environment variable and removing (or correcting) the "
                f"compiler_server_address line from your .forest_config file.")

        self.quilc_client = Client(_quilc_endpoint, timeout=timeout)

        self.qpu_compiler_endpoint = qpu_compiler_endpoint
        self._qpu_compiler_client: Optional[Union[Client,
                                                  HTTPCompilerClient]] = None

        self._device = device
        td = TargetDevice(isa=device.get_isa().to_dict(),
                          specs=None)  # type: ignore
        self.target_device = td
        self.name = name

        try:
            self.connect()
        except QuilcNotRunning as e:
            warnings.warn(
                f"{e}. Compilation using quilc will not be available.")
        except QPUCompilerNotRunning as e:
            warnings.warn(
                f"{e}. Compilation using the QPU compiler will not be available."
            )

    @property
    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

    def connect(self) -> None:
        self._connect_quilc()
        if self.qpu_compiler_client:
            self._connect_qpu_compiler()

    def _connect_quilc(self) -> None:
        try:
            quilc_version_dict = self.quilc_client.call("get_version_info")
            check_quilc_version(quilc_version_dict)
        except TimeoutError:
            raise QuilcNotRunning(
                f"Request to quilc at {self.quilc_client.endpoint} timed out. "
                "This could mean that quilc is not running, is not reachable, or is "
                "responding slowly.")

    def _connect_qpu_compiler(self) -> None:
        assert self.qpu_compiler_client is not None
        try:
            self.qpu_compiler_client.call("get_version_info")
        except TimeoutError:
            raise QPUCompilerNotRunning(
                f"Request to the QPU Compiler at {self.qpu_compiler_client.endpoint} "
                "timed out. "
                "This could mean that the service is not reachable or is responding slowly."
            )

    def get_version_info(self) -> Dict[str, Any]:
        quilc_version_info = self.quilc_client.call("get_version_info")
        if self.qpu_compiler_client:
            qpu_compiler_version_info = self.qpu_compiler_client.call(
                "get_version_info")
            return {
                "quilc": quilc_version_info,
                "qpu_compiler": qpu_compiler_version_info
            }
        return {"quilc": quilc_version_info}

    @_record_call
    def quil_to_native_quil(self,
                            program: Program,
                            *,
                            protoquil: Optional[bool] = None) -> Program:
        self._connect_quilc()
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.quilc_client.call("quil_to_native_quil",
                                          request,
                                          protoquil=protoquil).asdict()
        nq_program = parse_program(response["quil"])
        nq_program.native_quil_metadata = response["metadata"]
        nq_program.num_shots = program.num_shots
        return nq_program

    @_record_call
    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

    @_record_call
    def reset(self) -> None:
        """
        Reset the state of the QPUCompiler Client connections.
        """
        self._qpu_compiler_client = None
示例#4
0
class QPUCompiler(AbstractCompiler):
    @_record_call
    def __init__(
        self,
        quilc_endpoint: Optional[str],
        qpu_compiler_endpoint: Optional[str],
        device: AbstractDevice,
        timeout: float = 10,
        name: Optional[str] = None,
        *,
        session: Optional[ForestSession] = None,
    ) -> None:
        """
        Client to communicate with the Compiler Server.

        :param quilc_endpoint: TCP or IPC endpoint of the Quil Compiler (quilc).
        :param qpu_compiler_endpoint: TCP or IPC endpoint of the QPU Compiler.
        :param device: PyQuil Device object to use as compilation target.
        :param timeout: Number of seconds to wait for a response from the client.
        :param name: Name of the lattice being targeted.
        :param session: ForestSession object, which manages engagement and configuration.
        """

        if not (session or (quilc_endpoint and qpu_compiler_endpoint)):
            raise ValueError(
                "QPUCompiler requires either `session` or both of `quilc_endpoint` and "
                "`qpu_compiler_endpoint`.")

        self.session = session
        self.timeout = timeout

        if quilc_endpoint:
            _quilc_endpoint = quilc_endpoint
        elif self.session and self.session.config.quilc_url:
            _quilc_endpoint = self.session.config.quilc_url
        else:
            raise ValueError("Must provide a 'quilc_endpoint' or a 'session'")

        if not _quilc_endpoint.startswith("tcp://"):
            raise ValueError(
                f"PyQuil versions >= 2.4 can only talk to quilc "
                f"versions >= 1.4 over network RPCQ.  You've supplied the "
                f"endpoint '{quilc_endpoint}', but this doesn't look like a network "
                f"ZeroMQ address, which has the form 'tcp://domain:port'. "
                f"You might try clearing (or correcting) your COMPILER_URL "
                f"environment variable and removing (or correcting) the "
                f"compiler_server_address line from your .forest_config file.")

        self.quilc_client = Client(_quilc_endpoint, timeout=timeout)

        self.qpu_compiler_endpoint = qpu_compiler_endpoint
        self._qpu_compiler_client: Optional[Union[Client,
                                                  HTTPCompilerClient]] = None
        self._calibration_program = None

        self._device = device
        td = TargetDevice(isa=device.get_isa().to_dict(),
                          specs=None)  # type: ignore
        self.target_device = td
        self.name = name

        try:
            self.connect()
        except QuilcNotRunning as e:
            warnings.warn(
                f"{e}. Compilation using quilc will not be available.")
        except QPUCompilerNotRunning as e:
            warnings.warn(
                f"{e}. Compilation using the QPU compiler will not be available."
            )

    @property
    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

    def connect(self) -> None:
        self._connect_quilc()
        if self.qpu_compiler_client:
            self._connect_qpu_compiler()

    def _connect_quilc(self) -> None:
        try:
            quilc_version_dict = self.quilc_client.call("get_version_info")
            check_quilc_version(quilc_version_dict)
        except TimeoutError:
            raise QuilcNotRunning(
                f"Request to quilc at {self.quilc_client.endpoint} timed out. "
                "This could mean that quilc is not running, is not reachable, or is "
                "responding slowly.")

    def _connect_qpu_compiler(self) -> None:
        assert self.qpu_compiler_client is not None
        try:
            self.qpu_compiler_client.call("get_version_info")
        except TimeoutError:
            raise QPUCompilerNotRunning(
                f"Request to the QPU Compiler at {self.qpu_compiler_client.endpoint} "
                "timed out. "
                "This could mean that the service is not reachable or is responding slowly."
            )

    def get_version_info(self) -> Dict[str, Any]:
        quilc_version_info = self.quilc_client.call("get_version_info")
        if self.qpu_compiler_client:
            qpu_compiler_version_info = self.qpu_compiler_client.call(
                "get_version_info")
            return {
                "quilc": quilc_version_info,
                "qpu_compiler": qpu_compiler_version_info
            }
        return {"quilc": quilc_version_info}

    @_record_call
    def quil_to_native_quil(self,
                            program: Program,
                            *,
                            protoquil: Optional[bool] = None) -> Program:
        self._connect_quilc()
        request = NativeQuilRequest(quil=program.out(calibrations=False),
                                    target_device=self.target_device)
        response = self.quilc_client.call("quil_to_native_quil",
                                          request,
                                          protoquil=protoquil).asdict()
        nq_program = parse_program(response["quil"])
        nq_program.native_quil_metadata = response["metadata"]
        nq_program.num_shots = program.num_shots
        nq_program._calibrations = program.calibrations
        return nq_program

    @_record_call
    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

    @_record_call
    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

    @_record_call
    def refresh_calibration_program(self) -> None:
        """Refresh the calibration program cache."""
        self._calibration_program = self.get_calibration_program()

    @property
    def calibration_program(self) -> Program:
        """Cached calibrations."""
        if self._calibration_program is None:
            self.refresh_calibration_program()

        if self._calibration_program is None:
            raise RuntimeError("Could not refresh calibrations")
        else:
            return self._calibration_program

    @_record_call
    def reset(self) -> None:
        """
        Reset the state of the QPUCompiler Client connections.
        """
        self._qpu_compiler_client = None

    def set_timeout(self, timeout: float) -> None:
        """
        Set timeout for each individual stage of compilation.

        :param timeout: Timeout value for each compilation stage, in seconds. If the stage does not
            complete within this threshold, an exception is raised.
        """
        if timeout < 0:
            raise ValueError(f"Cannot set timeout to negative value {timeout}")

        self.timeout = timeout
        self.quilc_client.rpc_timeout = timeout