Skip to content

Commit

Permalink
Revert "Revert "remove Local variants of QVMCompiler and BenchmarkCon…
Browse files Browse the repository at this point in the history
…nection (rigetti#730)" (rigetti#781)"

This reverts commit af3edaf.
  • Loading branch information
Matthew Harrigan committed Jan 29, 2019
1 parent eed64fc commit 4b5c1c3
Show file tree
Hide file tree
Showing 12 changed files with 59 additions and 231 deletions.
4 changes: 2 additions & 2 deletions docs/source/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::

Expand Down
12 changes: 4 additions & 8 deletions docs/source/compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ Interacting with the Compiler
-----------------------------

After :ref:`downloading the SDK <sdkinstall>`, 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 |
Expand All @@ -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 <support@rigetti.com>.
[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`.

Expand Down
6 changes: 3 additions & 3 deletions docs/source/start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions pyquil/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
141 changes: 5 additions & 136 deletions pyquil/api/_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
37 changes: 2 additions & 35 deletions pyquil/api/_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand All @@ -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))
2 changes: 1 addition & 1 deletion pyquil/api/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
31 changes: 17 additions & 14 deletions pyquil/api/_quantum_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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]]:
Expand Down Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions pyquil/api/_qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 4b5c1c3

Please sign in to comment.