Ejemplo n.º 1
0
    def __init__(self,
                 endpoint: str,
                 user: str = "pyquil-user",
                 priority: int = 1) -> None:
        """
        A connection to the QPU.

        :param endpoint: Address to connect to the QPU server.
        :param user: A string identifying who's running jobs.
        :param priority: The priority with which to insert jobs into the QPU queue. Lower
                         integers correspond to higher priority.
        """

        if endpoint is None:
            raise RuntimeError(
                """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].""")

        self.client = Client(endpoint)
        self.user = user
        self._last_results: Dict[str, np.ndarray] = {}
        self.priority = priority

        super().__init__()
Ejemplo n.º 2
0
    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)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=None)

        try:
            self.connect()
        except QuilcNotRunning as e:
            warnings.warn(
                f"{e}. Compilation using quilc will not be available.")
Ejemplo n.º 3
0
class QVMCompiler(AbstractCompiler):
    @_record_call
    def __init__(self, endpoint: str, device: AbstractDevice) -> 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
        """
        self.client = Client(endpoint)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())

    def get_version_info(self) -> dict:
        return self.client.call('get_version_info')

    @_record_call
    def quil_to_native_quil(self, program: Program) -> Program:
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.client.call('quil_to_native_quil',
                                    request).asdict()  # type: Dict
        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))
Ejemplo n.º 4
0
 def reset(self) -> None:
     """
     Reset the state of the QVMCompiler quilc connection.
     """
     timeout = self.client.timeout
     self.client.close()
     self.client = Client(self.endpoint, timeout=timeout)
Ejemplo n.º 5
0
    def __init__(self,
                 endpoint: str,
                 device: AbstractDevice,
                 timeout: int = 10,
                 name: Optional[str] = None) -> 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
        :param timeout: Number of seconds to wait for a response from the client.
        :param name: Name of the lattice being targeted
        """

        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.client = Client(endpoint, timeout=timeout)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
        self.name = name
Ejemplo n.º 6
0
    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

        _quilc_endpoint = quilc_endpoint or self.session.config.quilc_url

        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 = None

        self._device = device
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=None)
        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."
            )
Ejemplo n.º 7
0
class QPUCompiler(AbstractCompiler):
    @_record_call
    def __init__(self,
                 endpoint: str,
                 device: AbstractDevice,
                 timeout: int = 10,
                 name: Optional[str] = None) -> 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
        :param timeout: Number of seconds to wait for a response from the client.
        :param name: Name of the lattice being targeted
        """

        self.client = Client(endpoint, timeout=timeout)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
        self.name = name

    def get_version_info(self) -> dict:
        return self.client.call('get_version_info')

    @_record_call
    def quil_to_native_quil(self, program: Program) -> Program:
        request = NativeQuilRequest(quil=program.out(), target_device=self.target_device)
        response = self.client.call('quil_to_native_quil', request).asdict()  # type: Dict
        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) -> BinaryExecutableResponse:
        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!")
        if self.name is not None:
            targeted_lattice = self.client.call('get_config_info')['lattice_name']
            if targeted_lattice and targeted_lattice != self.name:
                warnings.warn(f'You requested compilation for device {self.name}, '
                              f'but you are engaged on device {targeted_lattice}.')

        arithmetic_request = RewriteArithmeticRequest(quil=nq_program.out())
        arithmetic_response = self.client.call('resolve_gate_parameter_arithmetic', arithmetic_request)

        request = BinaryExecutableRequest(quil=arithmetic_response.quil, num_shots=nq_program.num_shots)
        response = self.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
        response.memory_descriptors = _collect_memory_descriptors(nq_program)
        response.ro_sources = _collect_classical_memory_write_locations(nq_program)
        return response
Ejemplo n.º 8
0
    def __init__(self, endpoint=None):
        """
        Client to communicate with the benchmarking data endpoint.

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

        self.client = Client(endpoint)
Ejemplo n.º 9
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.client = Client(endpoint, timeout=timeout)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
        self.connect()

    def connect(self):
        try:
            version_dict = self.get_version_info()
            check_quilc_version(version_dict)
        except TimeoutError:
            raise QuilcNotRunning(
                f'No quilc server running at {self.client.endpoint}')

    def get_version_info(self) -> dict:
        return self.client.call('get_version_info')

    @_record_call
    def quil_to_native_quil(self, program: Program) -> Program:
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.client.call('quil_to_native_quil',
                                    request).asdict()  # type: Dict
        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))
Ejemplo n.º 10
0
    def __init__(self, endpoint: str, device: AbstractDevice) -> 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
        """
        self.client = Client(endpoint)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
def refresh_client(client: Client, new_endpoint: str) -> Client:
    """
    Refresh the state of an RPCQ Client object, providing it a new endpoint.

    :param client: Stale RPCQ Client object
    :param new_endpoint: New RPC endpoint to use
    :return: New RPCQ Client object
    """
    timeout = client.timeout
    client.close()
    return Client(new_endpoint, timeout)
Ejemplo n.º 12
0
    def __init__(self, endpoint: str, user: str = "pyquil-user") -> None:
        """
        A connection to the QPU.

        :param endpoint: Address to connect to the QPU server.
        :param user: A string identifying who's running jobs.
        """
        super().__init__()
        self.client = Client(endpoint)
        self.user = user
        self._last_results: Dict[str, np.ndarray] = {}
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
    def __init__(self, endpoint: str, device: AbstractDevice, timeout: int = 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
        :param timeout: Number of seconds to wait for a response from the client.
        """

        self.client = Client(endpoint, timeout=timeout)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
Ejemplo n.º 15
0
    def __init__(self,
                 quilc_endpoint: str,
                 qpu_compiler_endpoint: Optional[str],
                 device: AbstractDevice,
                 timeout: int = 10,
                 name: Optional[str] = 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
        """

        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)
        if qpu_compiler_endpoint is not None:
            self.qpu_compiler_client = Client(qpu_compiler_endpoint,
                                              timeout=timeout)
        else:
            self.qpu_compiler_client = None
            warnings.warn(
                "It looks like you are initializing a QPUCompiler object without a "
                "qpu_compiler_address. If you didn't do this manually, then "
                "you probably don't have a qpu_compiler_address entry in your "
                "~/.forest_config file, meaning that you are not engaged to the QPU."
            )
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
        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.'
            )
Ejemplo n.º 16
0
class QPUCompiler(AbstractCompiler):
    @_record_call
    def __init__(self, endpoint: str, device: AbstractDevice) -> 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
        """

        self.client = Client(endpoint)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())

    def get_version_info(self) -> dict:
        return self.client.call('get_version_info')

    @_record_call
    def quil_to_native_quil(self, program: Program) -> Program:
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.client.call('quil_to_native_quil',
                                    request).asdict()  # type: Dict
        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) -> BinaryExecutableResponse:
        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!")

        request = BinaryExecutableRequest(quil=nq_program.out(),
                                          num_shots=nq_program.num_shots)
        response = self.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.memory_descriptors = _collect_memory_descriptors(nq_program)
        response.ro_sources = _collect_classical_memory_write_locations(
            nq_program)
        return response
Ejemplo n.º 17
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
Ejemplo n.º 18
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

        _quilc_endpoint = quilc_endpoint or self.session.config.quilc_url

        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 = None

        self._device = device
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=None)
        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) -> 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

    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",
                                                        rpc_timeout=1)
            check_quilc_version(quilc_version_dict)
        except TimeoutError:
            raise QuilcNotRunning(
                f"No quilc server reachable at {self.quilc_client.endpoint}")

    def _connect_qpu_compiler(self) -> None:
        try:
            self.qpu_compiler_client.call("get_version_info", rpc_timeout=1)
        except TimeoutError:
            raise QPUCompilerNotRunning(
                f"No QPU compiler server reachable at {self.qpu_compiler_client.endpoint}"
            )

    def get_version_info(self) -> dict:
        quilc_version_info = self.quilc_client.call("get_version_info",
                                                    rpc_timeout=1)
        if self.qpu_compiler_client:
            qpu_compiler_version_info = self.qpu_compiler_client.call(
                "get_version_info", rpc_timeout=1)
            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=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()  # type: Dict
        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_request = RewriteArithmeticRequest(quil=nq_program.out())
        arithmetic_response = self.quilc_client.call("rewrite_arithmetic",
                                                     arithmetic_request)

        request = BinaryExecutableRequest(quil=arithmetic_response.quil,
                                          num_shots=nq_program.num_shots)
        response = 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
        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):
        """
        Reset the state of the QPUCompiler Client connections.
        """
        self._qpu_compiler_client = None
Ejemplo n.º 19
0
class QPU(QAM):
    @_record_call
    def __init__(self, endpoint: str, user: str = "pyquil-user") -> None:
        """
        A connection to the QPU.

        :param endpoint: Address to connect to the QPU server.
        :param user: A string identifying who's running jobs.
        """
        super().__init__()
        self.client = Client(endpoint)
        self.user = user
        self._last_results: Dict[str, np.ndarray] = {}

    def get_version_info(self) -> dict:
        """
        Return version information for this QPU's execution engine and its dependencies.

        :return: Dictionary of version information.
        """
        return self.client.call('get_version_info')

    @_record_call
    def run(self):
        """
        Run a pyquil program on the QPU.

        This formats the classified data from the QPU server by stacking measured bits into
        an array of shape (trials, classical_addresses). The mapping of qubit to
        classical address is backed out from MEASURE instructions in the program, so
        only do measurements where there is a 1-to-1 mapping between qubits and classical
        addresses.

        :return: The QPU object itself.
        """
        super().run()

        request = QPURequest(program=self._executable.program,
                             patch_values=self._build_patch_values(),
                             id=str(uuid.uuid4()))

        job_id = self.client.call('execute_qpu_request', request=request, user=self.user)
        results = self._get_buffers(job_id)
        ro_sources = self._executable.ro_sources

        if results:
            bitstrings = _extract_bitstrings(ro_sources, results)
        else:
            bitstrings = None

        self._bitstrings = bitstrings
        self._last_results = results
        return self

    def _get_buffers(self, job_id: str) -> Dict[str, np.ndarray]:
        """
        Return the decoded result buffers for particular job_id.

        :param job_id: Unique identifier for the job in question
        :return: Decoded buffers or throw an error
        """
        buffers = self.client.call('get_buffers', job_id, wait=True)
        return {k: decode_buffer(v) for k, v in buffers.items()}

    def _build_patch_values(self) -> dict:
        patch_table = {}

        for name, spec in self._executable.memory_descriptors.items():
            # NOTE: right now we fake reading out measurement values into classical memory
            if name == "ro":
                continue
            patch_table[name] = [0] * spec.length

        for k, v in self._variables_shim.items():
            # NOTE: right now we fake reading out measurement values into classical memory
            if k.name == "ro":
                continue

            # floats stored in tsunami memory are expected to be in revolutions rather than radians.
            if isinstance(v, float):
                v /= 2 * np.pi

            patch_table[k.name][k.index] = v

        return patch_table
Ejemplo n.º 20
0
class QPUCompiler(AbstractCompiler):
    @_record_call
    def __init__(self,
                 endpoint: str,
                 device: AbstractDevice,
                 timeout: int = 10,
                 name: Optional[str] = None) -> 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
        :param timeout: Number of seconds to wait for a response from the client.
        :param name: Name of the lattice being targeted
        """

        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.client = Client(endpoint, timeout=timeout)
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
        self.name = name

    def get_version_info(self) -> dict:
        return self.client.call('get_version_info')

    @_record_call
    def quil_to_native_quil(self, program: Program) -> Program:
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.client.call('quil_to_native_quil',
                                    request).asdict()  # type: Dict
        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) -> BinaryExecutableResponse:
        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!")
        if self.name is not None:
            targeted_lattice = self.client.call(
                'get_config_info')['lattice_name']
            if targeted_lattice and targeted_lattice != self.name:
                warnings.warn(
                    f'You requested compilation for device {self.name}, '
                    f'but you are engaged on device {targeted_lattice}.')

        arithmetic_request = RewriteArithmeticRequest(quil=nq_program.out())
        arithmetic_response = self.client.call('rewrite_arithmetic',
                                               arithmetic_request)

        request = BinaryExecutableRequest(quil=arithmetic_response.quil,
                                          num_shots=nq_program.num_shots)
        response = self.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
        response.memory_descriptors = _collect_memory_descriptors(nq_program)
        response.ro_sources = _collect_classical_memory_write_locations(
            nq_program)
        return response
Ejemplo n.º 21
0
 def refresh_client(client: Client, new_endpoint: str) -> Client:
     timeout = client.timeout
     client.close()
     return Client(new_endpoint, timeout)
Ejemplo n.º 22
0
class BenchmarkConnection(AbstractBenchmarker):
    """
    Represents a connection to a server that generates benchmarking data.
    """
    @_record_call
    def __init__(self, endpoint=None):
        """
        Client to communicate with the benchmarking data endpoint.

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

        self.client = Client(endpoint)

    @_record_call
    def apply_clifford_to_pauli(self, clifford, pauli_in):
        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 PCP^{\dagger}.

        :param Program clifford: A Program that consists only of Clifford operations.
        :param PauliTerm pauli_in: A PauliTerm to be acted on by clifford via conjugation.
        :return: A PauliTerm corresponding to pauli_in * clifford * pauli_in^{\dagger}
        """

        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.j**phase_factor)
        clifford_qubits = clifford.get_qubits()
        pauli_qubits = pauli_in.get_qubits()
        all_qubits = sorted(set(pauli_qubits).union(set(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 *= PauliTerm(pauli, all_qubits[i])
        return pauli_out * pauli_in.coefficient

    @_record_call
    def generate_rb_sequence(self, depth, gateset, seed=None):
        """
        Construct a randomized benchmarking experiment on the given qubits, decomposing into
        gateset.

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

        :param int 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 list 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 int seed: A positive integer that seeds the random generation of the gate sequence.
        :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()

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

        payload = RandomizedBenchmarkingRequest(depth=depth,
                                                qubits=qubits,
                                                gateset=gateset_for_api,
                                                seed=seed)
        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))
Ejemplo n.º 23
0
class QPU(QAM):
    @_record_call
    def __init__(self,
                 endpoint: str,
                 user: str = "pyquil-user",
                 priority: int = 1) -> None:
        """
        A connection to the QPU.

        :param endpoint: Address to connect to the QPU server.
        :param user: A string identifying who's running jobs.
        :param priority: The priority with which to insert jobs into the QPU queue. Lower
                         integers correspond to higher priority.
        """

        if endpoint is None:
            raise RuntimeError(
                """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].""")

        self.client = Client(endpoint)
        self.user = user
        self._last_results: Dict[str, np.ndarray] = {}
        self.priority = priority

        super().__init__()

    def get_version_info(self) -> dict:
        """
        Return version information for this QPU's execution engine and its dependencies.

        :return: Dictionary of version information.
        """
        return self.client.call('get_version_info')

    @_record_call
    def load(self, executable):
        """
        Initialize a QAM into a fresh state. Load the executable and parse the expressions
        in the recalculation table (if any) into pyQuil Expression objects.

        :param executable: Load a compiled executable onto the QAM.
        """
        super().load(executable)
        if hasattr(self._executable, "recalculation_table"):
            recalculation_table = self._executable.recalculation_table
            for memory_reference, recalc_rule in recalculation_table.items():
                # We can only parse complete lines of Quil, so we wrap the arithmetic expression
                # in a valid Quil instruction to parse it.
                # TODO: This hack should be replaced after #687
                expression = parse(f"RZ({recalc_rule}) 0")[0].params[0]
                recalculation_table[memory_reference] = expression
        return self

    @_record_call
    def run(self, run_priority: Optional[int] = None):
        """
        Run a pyquil program on the QPU.

        This formats the classified data from the QPU server by stacking measured bits into
        an array of shape (trials, classical_addresses). The mapping of qubit to
        classical address is backed out from MEASURE instructions in the program, so
        only do measurements where there is a 1-to-1 mapping between qubits and classical
        addresses.

        :param run_priority: The priority with which to insert jobs into the QPU queue. Lower
                             integers correspond to higher priority. If not specified, the QPU
                             object's default priority is used.
        :return: The QPU object itself.
        """
        # This prevents a common error where users expect QVM.run()
        # and QPU.run() to be interchangeable. QPU.run() needs the
        # supplied executable to have been compiled, QVM.run() does not.
        if isinstance(self._executable, Program):
            raise TypeError(
                "It looks like you have provided a Program where an Executable"
                " is expected. Please use QuantumComputer.compile() to compile"
                " your program.")
        super().run()

        request = QPURequest(program=self._executable.program,
                             patch_values=self._build_patch_values(),
                             id=str(uuid.uuid4()))
        job_priority = run_priority if run_priority is not None else self.priority
        job_id = self.client.call('execute_qpu_request',
                                  request=request,
                                  user=self.user,
                                  priority=job_priority)
        results = self._get_buffers(job_id)
        ro_sources = self._executable.ro_sources

        if results:
            bitstrings = _extract_bitstrings(ro_sources, results)
        elif not ro_sources:
            warnings.warn(
                "You are running a QPU program with no MEASURE instructions. "
                "The result of this program will always be an empty array. Are "
                "you sure you didn't mean to measure some of your qubits?")
            bitstrings = np.zeros((0, 0), dtype=np.int64)
        else:
            bitstrings = None

        self._memory_results = defaultdict(lambda: None)
        self._memory_results["ro"] = bitstrings
        self._last_results = results

        return self

    def _get_buffers(self, job_id: str) -> Dict[str, np.ndarray]:
        """
        Return the decoded result buffers for particular job_id.

        :param job_id: Unique identifier for the job in question
        :return: Decoded buffers or throw an error
        """
        buffers = self.client.call('get_buffers', job_id, wait=True)
        return {k: decode_buffer(v) for k, v in buffers.items()}

    def _build_patch_values(self) -> dict:
        patch_values = {}

        # Now that we are about to run, we have to resolve any gate parameter arithmetic that was
        # saved in the executable's recalculation table, and add those values to the variables shim
        self._update_variables_shim_with_recalculation_table()

        # Initialize our patch table
        if hasattr(self._executable, "recalculation_table"):
            memory_ref_names = list(
                set(mr.name
                    for mr in self._executable.recalculation_table.keys()))
            if memory_ref_names != []:
                assert len(memory_ref_names) == 1, (
                    "We expected only one declared memory region for "
                    "the gate parameter arithmetic replacement references.")
                memory_reference_name = memory_ref_names[0]
                patch_values[memory_reference_name] = [0.0] * len(
                    self._executable.recalculation_table)

        for name, spec in self._executable.memory_descriptors.items():
            # NOTE: right now we fake reading out measurement values into classical memory
            if name == "ro":
                continue
            initial_value = 0.0 if spec.type == 'REAL' else 0
            patch_values[name] = [initial_value] * spec.length

        # Fill in our patch table
        for k, v in self._variables_shim.items():
            # NOTE: right now we fake reading out measurement values into classical memory
            if k.name == "ro":
                continue

            # floats stored in tsunami memory are expected to be in revolutions rather than radians.
            if isinstance(v, float):
                v /= 2 * np.pi

            patch_values[k.name][k.index] = v

        return patch_values

    def _update_variables_shim_with_recalculation_table(self):
        """
        Update self._variables_shim with the final values to be patched into the gate parameters,
        according to the arithmetic expressions in the original program.

        For example:

            DECLARE theta REAL
            DECLARE beta REAL
            RZ(3 * theta) 0
            RZ(beta+theta) 0

        gets translated to:

            DECLARE theta REAL
            DECLARE __P REAL[2]
            RZ(__P[0]) 0
            RZ(__P[1]) 0

        and the recalculation table will contain:

        {
            ParameterAref('__P', 0): Mul(3.0, <MemoryReference theta[0]>),
            ParameterAref('__P', 1): Add(<MemoryReference beta[0]>, <MemoryReference theta[0]>)
        }

        Let's say we've made the following two function calls:

            qpu.write_memory(region_name='theta', value=0.5)
            qpu.write_memory(region_name='beta', value=0.1)

        After executing this function, our self.variables_shim in the above example would contain
        the following:

        {
            ParameterAref('theta', 0): 0.5,
            ParameterAref('beta', 0): 0.1,
            ParameterAref('__P', 0): 1.5,       # (3.0) * theta[0]
            ParameterAref('__P', 1): 0.6        # beta[0] + theta[0]
        }

        Once the _variables_shim is filled, execution continues as with regular binary patching.
        """
        if not hasattr(self._executable, "recalculation_table"):
            # No recalculation table, no work to be done here.
            return
        for memory_reference, expression in self._executable.recalculation_table.items(
        ):
            # Replace the user-declared memory references with any values the user has written,
            # coerced to a float because that is how we declared it.
            self._variables_shim[memory_reference] = float(
                self._resolve_memory_references(expression))

    def _resolve_memory_references(
            self, expression: Expression) -> Union[float, int]:
        """
        Traverse the given Expression, and replace any Memory References with whatever values
        have been so far provided by the user for those memory spaces. Declared memory defaults
        to zero.

        :param expression: an Expression
        """
        if isinstance(expression, BinaryExp):
            left = self._resolve_memory_references(expression.op1)
            right = self._resolve_memory_references(expression.op2)
            return expression.fn(left, right)
        elif isinstance(expression, Function):
            return expression.fn(
                self._resolve_memory_references(expression.expression))
        elif isinstance(expression, Parameter):
            raise ValueError(
                f"Unexpected Parameter in gate expression: {expression}")
        elif isinstance(expression, float) or isinstance(expression, int):
            return expression
        elif isinstance(expression, MemoryReference):
            return self._variables_shim.get(
                ParameterAref(name=expression.name, index=expression.offset),
                0)
        else:
            raise ValueError(
                f"Unexpected expression in gate parameter: {expression}")

    @_record_call
    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()
        if pyquil_config.qpu_url is not None:
            endpoint = pyquil_config.qpu_url
        else:
            endpoint = self.client.endpoint
        self.client = refresh_client(self.client, endpoint)
Ejemplo n.º 24
0
class QPUCompiler(AbstractCompiler):
    @_record_call
    def __init__(self,
                 quilc_endpoint: str,
                 qpu_compiler_endpoint: Optional[str],
                 device: AbstractDevice,
                 timeout: int = 10,
                 name: Optional[str] = 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
        """

        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)
        if qpu_compiler_endpoint is not None:
            self.qpu_compiler_client = Client(qpu_compiler_endpoint,
                                              timeout=timeout)
        else:
            self.qpu_compiler_client = None
            warnings.warn(
                "It looks like you are initializing a QPUCompiler object without a "
                "qpu_compiler_address. If you didn't do this manually, then "
                "you probably don't have a qpu_compiler_address entry in your "
                "~/.forest_config file, meaning that you are not engaged to the QPU."
            )
        self.target_device = TargetDevice(isa=device.get_isa().to_dict(),
                                          specs=device.get_specs().to_dict())
        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.'
            )

    def connect(self):
        self._connect_quilc()
        if self.qpu_compiler_client:
            self._connect_qpu_compiler()

    def _connect_quilc(self):
        try:
            quilc_version_dict = self.quilc_client.call('get_version_info',
                                                        rpc_timeout=1)
            check_quilc_version(quilc_version_dict)
        except TimeoutError:
            raise QuilcNotRunning(
                f'No quilc server running at {self.quilc_client.endpoint}')

    def _connect_qpu_compiler(self):
        try:
            self.qpu_compiler_client.call('get_version_info', rpc_timeout=1)
        except TimeoutError:
            raise QPUCompilerNotRunning('No QPU compiler server running at '
                                        f'{self.qpu_compiler_client.endpoint}')

    def get_version_info(self) -> dict:
        quilc_version_info = self.quilc_client.call('get_version_info',
                                                    rpc_timeout=1)
        if self.qpu_compiler_client:
            qpu_compiler_version_info = self.qpu_compiler_client.call(
                'get_version_info', rpc_timeout=1)
            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) -> Program:
        self._connect_quilc()
        request = NativeQuilRequest(quil=program.out(),
                                    target_device=self.target_device)
        response = self.quilc_client.call('quil_to_native_quil',
                                          request).asdict()  # type: Dict
        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 ValueError(
                "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!")
        if self.name is not None:
            targeted_lattice = self.qpu_compiler_client.call(
                'get_config_info')['lattice_name']
            if targeted_lattice and targeted_lattice != self.name:
                warnings.warn(
                    f'You requested compilation for device {self.name}, '
                    f'but you are engaged on device {targeted_lattice}.')

        arithmetic_request = RewriteArithmeticRequest(quil=nq_program.out())
        arithmetic_response = self.quilc_client.call('rewrite_arithmetic',
                                                     arithmetic_request)

        request = BinaryExecutableRequest(quil=arithmetic_response.quil,
                                          num_shots=nq_program.num_shots)
        response = 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
        response.memory_descriptors = _collect_memory_descriptors(nq_program)
        response.ro_sources = _collect_classical_memory_write_locations(
            nq_program)
        return response