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 __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.")
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))
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)
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 __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." )
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
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)
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))
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)
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_active_lattice() -> Optional[str]: """ Try to query which lattice we're engaged to from QCS. Returns the lattice name if available, otherwise None. """ from rpcq import Client from pyquil.api._config import PyquilConfig try: qcc = Client(endpoint=PyquilConfig().qpu_compiler_url, timeout=1) return qcc.call("get_config_info")["lattice_name"] except: return None
def __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())
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.' )
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
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
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
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
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
def refresh_client(client: Client, new_endpoint: str) -> Client: timeout = client.timeout client.close() return Client(new_endpoint, timeout)
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))
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)
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