diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 893fcea61..bff66bbd8 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -69,8 +69,8 @@ where isn't found, which is the correct endpoint when you run the QVM locally with ``qvm -S``. - ``compiler_server_address``: This is the endpoint where pyQuil will try to communicate with the compiler server. On a QMI, this points to a provided compiler server instance. On a local installation, this should be set to the server - endpoint for a locally running ``quilc`` instance. However, pyQuil will use the default value ``http://localhost:6000`` - if this isn't set, which is the correct endpoint when you run ``quilc`` locally with ``quilc -S``. + endpoint for a locally running ``quilc`` instance. However, pyQuil will use the default value ``tcp://localhost:5555`` + if this isn't set, which is the correct endpoint when you run ``quilc`` locally with ``quilc -R``. .. note:: diff --git a/docs/source/compiler.rst b/docs/source/compiler.rst index 59a85c2d6..ef9123ab6 100644 --- a/docs/source/compiler.rst +++ b/docs/source/compiler.rst @@ -19,11 +19,11 @@ Interacting with the Compiler ----------------------------- After :ref:`downloading the SDK `, the Quil Compiler, ``quilc`` is available on your local machine. -You can initialize a local ``quilc`` server by typing ``quilc -S`` into your terminal. You should see the following message. +You can initialize a local ``quilc`` server by typing ``quilc -R`` into your terminal. You should see the following message. .. code:: python - $ quilc -S + $ quilc -R +-----------------+ | W E L C O M E | | T O T H E | @@ -33,12 +33,8 @@ You can initialize a local ``quilc`` server by typing ``quilc -S`` into your ter +-----------------+ Copyright (c) 2018 Rigetti Computing. - This is a part of the Forest SDK. By using this program - you agree to the End User License Agreement (EULA) supplied - with this program. If you did not receive the EULA, please - contact . - - [2018-11-06 10:59:22] Starting server: 0.0.0.0 : 6000. + ... - Launching quilc. + ... - Spawning server at (tcp://*:5555) . To get a description of ``quilc``, and options and examples of its command line use, see :ref:`quilc_man`. diff --git a/docs/source/start.rst b/docs/source/start.rst index 89eafa17b..ecebf6d87 100644 --- a/docs/source/start.rst +++ b/docs/source/start.rst @@ -213,10 +213,10 @@ terminal. ### CONSOLE 2 - $ quilc -S + $ quilc -R - Welcome to the Rigetti Quil Compiler - [2018-09-19 11:22:37] Starting server: 0.0.0.0 : 6000. + ... - Launching quilc. + ... - Spawning server at (tcp://*:5555) . That's it! You're all set up to run pyQuil locally. Your programs will make requests to these server endpoints to compile your Quil diff --git a/pyquil/api/__init__.py b/pyquil/api/__init__.py index abd2f5e22..12da9a17f 100644 --- a/pyquil/api/__init__.py +++ b/pyquil/api/__init__.py @@ -18,15 +18,15 @@ """ import warnings -__all__ = ['QVMConnection', 'LocalQVMCompiler', 'QVMCompiler', 'QPUCompiler', +__all__ = ['QVMConnection', 'QVMCompiler', 'QPUCompiler', 'Job', 'Device', 'ForestConnection', 'pyquil_protect', 'WavefunctionSimulator', 'QuantumComputer', 'list_quantum_computers', 'get_qc', 'QAM', 'QVM', 'QPU', 'QPUConnection', - 'BenchmarkConnection', 'LocalBenchmarkConnection', 'get_benchmarker'] + 'BenchmarkConnection', 'get_benchmarker'] from pyquil.api._base_connection import ForestConnection -from pyquil.api._benchmark import BenchmarkConnection, LocalBenchmarkConnection, get_benchmarker -from pyquil.api._compiler import QVMCompiler, QPUCompiler, LocalQVMCompiler +from pyquil.api._benchmark import BenchmarkConnection, get_benchmarker +from pyquil.api._compiler import QVMCompiler, QPUCompiler from pyquil.api._error_reporting import pyquil_protect from pyquil.api._job import Job from pyquil.api._qam import QAM diff --git a/pyquil/api/_benchmark.py b/pyquil/api/_benchmark.py index e00e52614..bdca8e760 100644 --- a/pyquil/api/_benchmark.py +++ b/pyquil/api/_benchmark.py @@ -32,14 +32,14 @@ class BenchmarkConnection(AbstractBenchmarker): """ @_record_call - def __init__(self, endpoint=None): + def __init__(self, endpoint=None, timeout=None): """ Client to communicate with the benchmarking data endpoint. :param endpoint: TCP or IPC endpoint of the Compiler Server """ - self.client = Client(endpoint) + self.client = Client(endpoint, timeout=timeout) @_record_call def apply_clifford_to_pauli(self, clifford, pauli_in): @@ -138,148 +138,17 @@ def generate_rb_sequence(self, depth, gateset, seed=None, interleaver=None): return list(reversed(programs)) -class LocalBenchmarkConnection(AbstractBenchmarker): - """ - Represents a connection to a locally-running server that generates randomized benchmarking data. - """ - - @_record_call - def __init__(self, endpoint=None): - super(LocalBenchmarkConnection, self).__init__() - self.endpoint = endpoint - self.session = get_session() - - @staticmethod - def _clifford_application_payload(clifford, pauli): - """ - Prepares a JSON payload for conjugating a Pauli by a Clifford. - - See :py:func:`apply_clifford_to_pauli`. - - :param Program clifford: A Program that consists only of Clifford operations. - :param PauliTerm pauli: A PauliTerm to be acted on by clifford via conjugation. - :return: The JSON payload, with keys "clifford" and "pauli". - """ - indices_and_terms = zip(*list(pauli.operations_as_set())) - return {"clifford": clifford.out(), - "pauli": list(indices_and_terms)} - - @_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} - """ - payload = self._clifford_application_payload(clifford, pauli_in) - phase_factor, paulis = post_json(self.session, self.endpoint + "/apply-clifford", - payload).json() - 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 - - @staticmethod - def _rb_sequence_payload(depth, gateset, seed=None, interleaver=None): - """ - Prepares a JSON payload for generating a randomized benchmarking sequence. - - See :py:func:`generate_rb_sequence`. - - :param int depth: The number of cliffords per rb sequences to generate. - :param list gateset: A list of Gate objects that make up the gateset to decompose - the Cliffords into. - :return: The JSON payload, with keys "depth", "qubits", and "gateset". - """ - # 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())) - n_qubits = len(gateset_as_program.get_qubits()) - gateset_for_api = gateset_as_program.out().splitlines() - payload = {"depth": depth, - "qubits": n_qubits, - "gateset": gateset_for_api, - "seed": seed} - - if interleaver: - assert(isinstance(interleaver, Program)) - payload["interleaver"] = interleaver.out() - - return payload - - @_record_call - def generate_rb_sequence(self, depth, gateset, seed=None, interleaver=None): - """ - Construct a randomized benchmarking experiment on the given qubits, decomposing into - gateset. If interleaver is not provided, the returned sequence will have the form - - C_1 C_2 ... C_(depth-1) C_inv , - - where each C is a Clifford element drawn from gateset, C_{< depth} are randomly selected, - and C_inv is selected so that the entire sequence composes to the identity. If an - interleaver G (which must be a Clifford, and which will be decomposed into the native - gateset) is provided, then the sequence instead takes the form - - C_1 G C_2 G ... C_(depth-1) G C_inv . - - The JSON response is a list of lists of indices, or Nones. In the former case, they are the - index of the gate in the gateset. - - :param 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 seed: A positive integer used to seed the PRNG. - :param interleaver: A Program object that encodes a Clifford element. - :return: A list of pyquil programs. Each pyquil program is a circuit that represents an - element of the Clifford group. When these programs are composed, the resulting Program - will be the randomized benchmarking experiment of the desired depth. e.g. if the return - programs are called cliffords then `sum(cliffords, Program())` will give the randomized - benchmarking experiment, which will compose to the identity program. - """ - depth = int(depth) # needs to be jsonable, no np.int64 please! - payload = self._rb_sequence_payload(depth, gateset, seed=seed, interleaver=interleaver) - response = post_json(self.session, self.endpoint + "/rb", payload).json() - programs = [] - for clifford in response: - 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)) - - -def get_benchmarker(endpoint: str = None): +def get_benchmarker(endpoint: str = None, timeout: float = None): """ Retrieve an instance of the appropriate AbstractBenchmarker subclass for a given endpoint. :param endpoint: Benchmarking sequence server address. Defaults to the setting in the user's pyQuil config. + :param timeout: Number of seconds to wait before giving up on a call. :return: Instance of an AbstractBenchmarker subclass, connected to the given endpoint. """ if endpoint is None: config = PyquilConfig() endpoint = config.compiler_url - if endpoint.startswith("http"): - return LocalBenchmarkConnection(endpoint=endpoint) - elif endpoint.startswith("tcp"): - return BenchmarkConnection(endpoint=endpoint) - else: - raise ValueError("Protocol for RB endpoint must be HTTP or TCP.") + return BenchmarkConnection(endpoint=endpoint, timeout=timeout) diff --git a/pyquil/api/_compiler.py b/pyquil/api/_compiler.py index 54599f3f9..af0dc418e 100644 --- a/pyquil/api/_compiler.py +++ b/pyquil/api/_compiler.py @@ -189,14 +189,14 @@ def native_quil_to_executable(self, nq_program: Program) -> BinaryExecutableResp class QVMCompiler(AbstractCompiler): @_record_call - def __init__(self, endpoint: str, device: AbstractDevice) -> None: + def __init__(self, endpoint: str, device: AbstractDevice, timeout: float = 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 """ - self.client = Client(endpoint) + self.client = Client(endpoint, timeout=timeout) self.target_device = TargetDevice(isa=device.get_isa().to_dict(), specs=device.get_specs().to_dict()) @@ -217,36 +217,3 @@ def native_quil_to_executable(self, nq_program: Program) -> PyQuilExecutableResp return PyQuilExecutableResponse( program=nq_program.out(), attributes=_extract_attribute_dictionary_from_program(nq_program)) - - -class LocalQVMCompiler(AbstractCompiler): - def __init__(self, endpoint: str, device: AbstractDevice) -> None: - """ - Client to communicate with a locally executing quilc instance. - - :param endpoint: HTTP endpoint of the quilc instance. - :param device: PyQuil Device object to use as the compilation target. - """ - self.endpoint = endpoint - self.isa = device.get_isa() - self.specs = device.get_specs() - - self._connection = ForestConnection(sync_endpoint=endpoint) - self.session = self._connection.session # backwards compatibility - - def get_version_info(self) -> dict: - return self._connection._quilc_get_version_info() - - def quil_to_native_quil(self, program: Program) -> Program: - response = self._connection._quilc_compile(program, self.isa, self.specs) - - compiled_program = Program(response['compiled-quil']) - compiled_program.native_quil_metadata = response['metadata'] - compiled_program.num_shots = program.num_shots - - return compiled_program - - def native_quil_to_executable(self, nq_program: Program): - return PyQuilExecutableResponse( - program=nq_program.out(), - attributes=_extract_attribute_dictionary_from_program(nq_program)) diff --git a/pyquil/api/_config.py b/pyquil/api/_config.py index d8ac7f14b..2d3d6390b 100644 --- a/pyquil/api/_config.py +++ b/pyquil/api/_config.py @@ -86,7 +86,7 @@ class PyquilConfig(object): "file": FOREST_CONFIG, "section": "Rigetti Forest", "name": "compiler_server_address", - "default": "http://127.0.0.1:6000" + "default": "tcp://127.0.0.1:5555" } def __init__(self): diff --git a/pyquil/api/_quantum_computer.py b/pyquil/api/_quantum_computer.py index 1b9864be5..2c3bb5c7d 100644 --- a/pyquil/api/_quantum_computer.py +++ b/pyquil/api/_quantum_computer.py @@ -24,7 +24,7 @@ import numpy as np from rpcq.messages import BinaryExecutableResponse, Message, PyQuilExecutableResponse -from pyquil.api._compiler import QVMCompiler, QPUCompiler, LocalQVMCompiler +from pyquil.api._compiler import QPUCompiler, QVMCompiler from pyquil.api._config import PyquilConfig from pyquil.api._devices import get_lattice, list_lattices from pyquil.api._error_reporting import _record_call @@ -363,17 +363,6 @@ def _canonicalize_name(prefix, qvm_type, noisy): return name -def _get_qvm_compiler_based_on_endpoint(endpoint: str = None, - device: AbstractDevice = None) \ - -> AbstractCompiler: - if endpoint.startswith("http"): - return LocalQVMCompiler(endpoint=endpoint, device=device) - elif endpoint.startswith("tcp"): - return QVMCompiler(endpoint=endpoint, device=device) - else: - raise ValueError("Protocol for QVM compiler endpoints must be HTTP or TCP.") - - def _get_qvm_or_pyqvm(qvm_type, connection, noise_model=None, device=None, requires_executable=False): if qvm_type == 'qvm': @@ -414,7 +403,7 @@ def _get_qvm_qc(name: str, qvm_type: str, device: AbstractDevice, noise_model: N device=device, requires_executable=requires_executable), device=device, - compiler=_get_qvm_compiler_based_on_endpoint( + compiler=QVMCompiler( device=device, endpoint=connection.compiler_endpoint)) @@ -638,6 +627,20 @@ def get_qc(name: str, *, as_qvm: bool = None, noisy: bool = None, device=device, name=prefix)) + if noisy: + noise_model = device.noise_model + else: + noise_model = None + + return QuantumComputer(name=name, + qam=QVM(connection=connection, + noise_model=noise_model, + requires_executable=True), + device=device, + compiler=QVMCompiler( + device=device, + endpoint=connection.compiler_endpoint)) + @contextmanager def local_qvm() -> Iterator[Tuple[subprocess.Popen, subprocess.Popen]]: @@ -670,7 +673,7 @@ def local_qvm() -> Iterator[Tuple[subprocess.Popen, subprocess.Popen]]: stdout=subprocess.PIPE, stderr=subprocess.PIPE) - quilc = subprocess.Popen(['quilc', '-S'], + quilc = subprocess.Popen(['quilc', '-RP'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/pyquil/api/_qvm.py b/pyquil/api/_qvm.py index 0868a8fb2..39b8a60c1 100644 --- a/pyquil/api/_qvm.py +++ b/pyquil/api/_qvm.py @@ -23,7 +23,7 @@ from pyquil.api._base_connection import (validate_qubit_list, validate_noise_probabilities, TYPE_MULTISHOT_MEASURE, TYPE_WAVEFUNCTION, TYPE_EXPECTATION, post_json, ForestConnection) -from pyquil.api._compiler import (LocalQVMCompiler, +from pyquil.api._compiler import (QVMCompiler, _extract_program_from_pyquil_executable_response) from pyquil.api._config import PyquilConfig from pyquil.api._error_reporting import _record_call @@ -86,7 +86,7 @@ def __init__(self, device=None, endpoint=None, """) self.noise_model = device.noise_model if device else None - self.compiler = LocalQVMCompiler(endpoint=compiler_endpoint, device=device) if device \ + self.compiler = QVMCompiler(endpoint=compiler_endpoint, device=device) if device \ else None self.sync_endpoint = endpoint diff --git a/pyquil/tests/conftest.py b/pyquil/tests/conftest.py index 396becf00..ae879a845 100644 --- a/pyquil/tests/conftest.py +++ b/pyquil/tests/conftest.py @@ -5,13 +5,13 @@ import pytest from requests import RequestException -from pyquil.api import (QVMConnection, LocalQVMCompiler, ForestConnection, +from pyquil.api import (QVMConnection, QVMCompiler, ForestConnection, get_benchmarker, local_qvm) from pyquil.api._config import PyquilConfig from pyquil.api._errors import UnknownApiError from pyquil.device import Device from pyquil.gates import I -from pyquil.paulis import sI +from pyquil.paulis import sX from pyquil.quil import Program @@ -146,10 +146,10 @@ def qvm(): def compiler(test_device): try: config = PyquilConfig() - compiler = LocalQVMCompiler(endpoint=config.compiler_url, device=test_device) + compiler = QVMCompiler(endpoint=config.compiler_url, device=test_device, timeout=1) compiler.quil_to_native_quil(Program(I(0))) return compiler - except (RequestException, UnknownApiError) as e: + except (RequestException, UnknownApiError, TimeoutError) as e: return pytest.skip("This test requires compiler connection: {}".format(e)) @@ -166,9 +166,9 @@ def forest(): @pytest.fixture(scope='session') def benchmarker(): try: - bm = get_benchmarker() - bm.apply_clifford_to_pauli(Program(I(0)), sI(0)) - except RequestException as e: + bm = get_benchmarker(timeout=1) + bm.apply_clifford_to_pauli(Program(I(0)), sX(0)) + except (RequestException, TimeoutError) as e: return pytest.skip("This test requires a running local benchmarker endpoint (ie quilc): {}" .format(e)) diff --git a/pyquil/tests/test_api.py b/pyquil/tests/test_api.py index 55d3dd938..0fc23749a 100644 --- a/pyquil/tests/test_api.py +++ b/pyquil/tests/test_api.py @@ -35,7 +35,7 @@ RandomizedBenchmarkingRequest, RandomizedBenchmarkingResponse) from pyquil.api import (QVMConnection, QPUCompiler, BenchmarkConnection, - get_qc, LocalQVMCompiler, QVMCompiler, LocalBenchmarkConnection) + get_qc, QVMCompiler) from pyquil.api._base_connection import validate_noise_probabilities, validate_qubit_list, \ prepare_register_list from pyquil.api._config import PyquilConfig @@ -50,7 +50,7 @@ BELL_STATE_MEASURE = Program(H(0), CNOT(0, 1), MEASURE(0, 0), MEASURE(1, 1)) COMPILED_BELL_STATE = Program([ Declare("ro", "BIT", 2), - Pragma("EXPECTED_REWIRING", ('"#(0 1 2 3)"',)), + Pragma("EXPECTED_REWIRING", ('"#(0 1)"',)), RZ(pi / 2, 0), RX(pi / 2, 0), RZ(-pi / 2, 1), @@ -59,9 +59,9 @@ RZ(-pi / 2, 0), RX(-pi / 2, 1), RZ(pi / 2, 1), - Pragma("CURRENT_REWIRING", ('"#(0 1 2 3)"',)), - Pragma("EXPECTED_REWIRING", ('"#(0 1 2 3)"',)), - Pragma("CURRENT_REWIRING", ('"#(0 1 2 3)"',)), + Pragma("CURRENT_REWIRING", ('"#(0 1)"',)), + Pragma("EXPECTED_REWIRING", ('"#(0 1)"',)), + Pragma("CURRENT_REWIRING", ('"#(0 1)"',)), ]) DUMMY_ISA_DICT = {"1Q": {"0": {}, "1": {}}, "2Q": {"0-1": {}}} DUMMY_ISA = ISA.from_dict(DUMMY_ISA_DICT) @@ -243,14 +243,8 @@ def test_prepare_register_list(): # --------------------- -def test_get_qc_returns_local_qvm_compiler(): - with patch.dict('os.environ', {"COMPILER_URL": "http://127.0.0.1:7000"}): - qc = get_qc("9q-generic-qvm") - assert isinstance(qc.compiler, LocalQVMCompiler) - - def test_get_qc_returns_remote_qvm_compiler(): - with patch.dict('os.environ', {"COMPILER_URL": "tcp://192.168.0.0:5555"}): + with patch.dict('os.environ', {"COMPILER_URL": "tcp://192.168.0.0:5550"}): qc = get_qc("9q-generic-qvm") assert isinstance(qc.compiler, QVMCompiler) @@ -308,7 +302,7 @@ def conjugate_pauli_by_clifford(payload: ConjugateByCliffordRequest) -> Conjugat @pytest.fixture def m_endpoints(): - return "tcp://127.0.0.1:5555", "tcp://*:5555" + return "tcp://127.0.0.1:5550", "tcp://*:5550" def run_mock(_, endpoint): @@ -367,7 +361,7 @@ def test_conjugate_request(server, mock_rb_cxn): def test_local_rb_sequence(benchmarker): config = PyquilConfig() if config.compiler_url is not None: - cxn = LocalBenchmarkConnection(endpoint=config.compiler_url) + cxn = BenchmarkConnection(endpoint=config.compiler_url) response = cxn.generate_rb_sequence(2, [PHASE(np.pi / 2, 0), H(0)], seed=52) assert [prog.out() for prog in response] == \ ["H 0\nPHASE(pi/2) 0\nH 0\nPHASE(pi/2) 0\nPHASE(pi/2) 0\n", @@ -377,7 +371,7 @@ def test_local_rb_sequence(benchmarker): def test_local_conjugate_request(benchmarker): config = PyquilConfig() if config.compiler_url is not None: - cxn = LocalBenchmarkConnection(endpoint=config.compiler_url) + cxn = BenchmarkConnection(endpoint=config.compiler_url) response = cxn.apply_clifford_to_pauli(Program("H 0"), PauliTerm("X", 0, 1.0)) assert isinstance(response, PauliTerm) assert str(response) == "(1+0j)*Z0" diff --git a/pyquil/tests/test_qvm.py b/pyquil/tests/test_qvm.py index 4cbd556d6..612bbb15f 100644 --- a/pyquil/tests/test_qvm.py +++ b/pyquil/tests/test_qvm.py @@ -5,7 +5,7 @@ from rpcq.messages import PyQuilExecutableResponse from pyquil import Program -from pyquil.api import QVM, ForestConnection, LocalQVMCompiler +from pyquil.api import QVM, ForestConnection, QVMCompiler from pyquil.api._compiler import _extract_program_from_pyquil_executable_response from pyquil.device import NxDevice from pyquil.gates import MEASURE, X, CNOT, H @@ -65,10 +65,9 @@ def test_qvm_run_no_measure(forest: ForestConnection): assert bitstrings.shape == (100, 0) -def test_roundtrip_pyquilexecutableresponse(): +def test_roundtrip_pyquilexecutableresponse(compiler): p = Program(H(10), CNOT(10, 11)) - lcqvm = LocalQVMCompiler(endpoint=None, device=NxDevice(nx.complete_graph(3))) - pqer = lcqvm.native_quil_to_executable(p) + pqer = compiler.native_quil_to_executable(p) p2 = _extract_program_from_pyquil_executable_response(pqer) for i1, i2 in zip(p, p2): assert i1 == i2