Skip to content

Commit

Permalink
feat: Quilc clients support (#1638)
Browse files Browse the repository at this point in the history
* new: Quilc client support

This is to support the (future) introduction of alternative quilc/qvm clients
(i.e. "libquil").
  • Loading branch information
notmgsk committed Sep 11, 2023
1 parent 7d97834 commit 42c328a
Show file tree
Hide file tree
Showing 11 changed files with 775 additions and 660 deletions.
1,220 changes: 637 additions & 583 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ rpcq = "^3.10.0"
pydantic = "^1.10.7"
networkx = ">=2.5"
importlib-metadata = { version = ">=3.7.3,<5", python = "<3.8" }
qcs-sdk-python = "0.11.0"
qcs-sdk-python = "0.12.0"
tenacity = "^8.2.2"
types-python-dateutil = "^2.8.19"
types-retry = "^0.9.9"
Expand Down
6 changes: 4 additions & 2 deletions pyquil/api/_abstract_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import json

from qcs_sdk import QCSClient
from qcs_sdk.compiler.quilc import compile_program, CompilerOpts, TargetDevice
from qcs_sdk.compiler.quilc import compile_program, TargetDevice, CompilerOpts, QuilcClient

from pyquil._version import pyquil_version
from pyquil.api._compiler_client import CompilerClient
Expand Down Expand Up @@ -78,6 +78,7 @@ def __init__(
quantum_processor: AbstractQuantumProcessor,
timeout: float,
client_configuration: Optional[QCSClient] = None,
quilc_client: Optional[QuilcClient] = None,
) -> None:
self.quantum_processor = quantum_processor
self._timeout = timeout
Expand All @@ -87,6 +88,7 @@ def __init__(
self._compiler_client = CompilerClient(
client_configuration=self._client_configuration,
request_timeout=timeout,
quilc_client=quilc_client,
)

def get_version_info(self) -> Dict[str, Any]:
Expand All @@ -111,7 +113,7 @@ def quil_to_native_quil(self, program: Program, *, protoquil: Optional[bool] = N
result = compile_program(
quil=program.out(calibrations=False),
target=target_device,
client=self._client_configuration,
client=self._compiler_client.quilc_client,
options=CompilerOpts(protoquil=protoquil, timeout=self._compiler_client.timeout),
)

Expand Down
4 changes: 4 additions & 0 deletions pyquil/api/_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
translate,
TranslationOptions as QPUCompilerAPIOptions,
)
from qcs_sdk.compiler.quilc import QuilcClient
from rpcq.messages import ParameterSpec

from pyquil.api._abstract_compiler import AbstractCompiler, EncryptedProgram, QuantumExecutable
Expand Down Expand Up @@ -76,6 +77,7 @@ def __init__(
timeout: float = 10.0,
client_configuration: Optional[QCSClient] = None,
api_options: Optional[QPUCompilerAPIOptions] = None,
quilc_client: Optional[QuilcClient] = None,
) -> None:
"""
Instantiate a new QPU compiler client.
Expand Down Expand Up @@ -171,6 +173,7 @@ def __init__(
quantum_processor: AbstractQuantumProcessor,
timeout: float = 10.0,
client_configuration: Optional[QCSClient] = None,
quilc_client: Optional[QuilcClient] = None,
) -> None:
"""
Client to communicate with compiler.
Expand All @@ -183,6 +186,7 @@ def __init__(
quantum_processor=quantum_processor,
timeout=timeout,
client_configuration=client_configuration,
quilc_client=quilc_client,
)

def native_quil_to_executable(self, nq_program: Program, **kwargs: Any) -> QuantumExecutable:
Expand Down
17 changes: 13 additions & 4 deletions pyquil/api/_compiler_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
RandomizedBenchmarkingRequest,
GenerateRandomizedBenchmarkingSequenceResponse,
NativeQuilMetadata,
QuilcClient,
)
from rpcq.messages import TargetDevice as TargetQuantumProcessor

Expand Down Expand Up @@ -109,6 +110,7 @@ def __init__(
*,
client_configuration: QCSClient,
request_timeout: float = 10.0,
quilc_client: Optional[QuilcClient] = None,
) -> None:
"""
Instantiate a new compiler client.
Expand All @@ -121,6 +123,13 @@ def __init__(
if not base_url.startswith("tcp://"):
raise ValueError(f"Expected compiler URL '{base_url}' to start with 'tcp://'")

if quilc_client is None:
self.quilc_client = QuilcClient.new_rpcq(base_url)
elif isinstance(quilc_client, QuilcClient):
self.quilc_client = quilc_client
else:
raise TypeError(f"Unsupported type for Quilc client: {quilc_client}")

self.base_url = base_url
self.timeout = request_timeout

Expand All @@ -129,7 +138,7 @@ def get_version(self) -> str:
Get version info for compiler server.
"""

return get_version_info(client=self._client_configuration)
return get_version_info(client=self.quilc_client)

def compile_to_native_quil(self, request: CompileToNativeQuilRequest) -> CompileToNativeQuilResponse:
"""
Expand All @@ -141,7 +150,7 @@ def compile_to_native_quil(self, request: CompileToNativeQuilRequest) -> Compile
result = compile_program(
quil=request.program,
target=target_device,
client=self._client_configuration,
client=self.quilc_client,
options=CompilerOpts(protoquil=request.protoquil, timeout=self.timeout),
)
return CompileToNativeQuilResponse(native_program=result.program, metadata=result.native_quil_metadata)
Expand All @@ -150,12 +159,12 @@ def conjugate_pauli_by_clifford(self, request: ConjugateByCliffordRequest) -> Co
"""
Conjugate a Pauli element by a Clifford element.
"""
return conjugate_pauli_by_clifford(request=request, client=self._client_configuration)
return conjugate_pauli_by_clifford(request=request, client=self.quilc_client)

def generate_randomized_benchmarking_sequence(
self, request: RandomizedBenchmarkingRequest
) -> GenerateRandomizedBenchmarkingSequenceResponse:
"""
Generate a randomized benchmarking sequence.
"""
return generate_randomized_benchmarking_sequence(request=request, client=self._client_configuration)
return generate_randomized_benchmarking_sequence(request=request, client=self.quilc_client)
36 changes: 33 additions & 3 deletions pyquil/api/_quantum_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

from qcs_sdk import QCSClient
from qcs_sdk.qpu import list_quantum_processors
from qcs_sdk.compiler.quilc import QuilcClient
from qcs_sdk.qvm import QVMClient

from pyquil.api._abstract_compiler import AbstractCompiler, QuantumExecutable
from pyquil.api._compiler import QPUCompiler, QVMCompiler
Expand Down Expand Up @@ -497,14 +499,14 @@ def _canonicalize_name(prefix: str, qvm_type: Optional[str], noisy: bool) -> str

def _get_qvm_or_pyqvm(
*,
client_configuration: QCSClient,
qvm_type: str,
qvm_client: Optional[QVMClient],
noise_model: Optional[NoiseModel],
quantum_processor: Optional[AbstractQuantumProcessor],
execution_timeout: float,
) -> Union[QVM, PyQVM]:
if qvm_type == "qvm":
return QVM(noise_model=noise_model, timeout=execution_timeout, client_configuration=client_configuration)
return QVM(noise_model=noise_model, timeout=execution_timeout, client=qvm_client)
elif qvm_type == "pyqvm":
assert quantum_processor is not None
return PyQVM(n_qubits=quantum_processor.qubit_topology().number_of_nodes())
Expand All @@ -521,6 +523,8 @@ def _get_qvm_qc(
compiler_timeout: float,
execution_timeout: float,
noise_model: Optional[NoiseModel],
quilc_client: Optional[QuilcClient] = None,
qvm_client: Optional[QVMClient] = None,
) -> QuantumComputer:
"""Construct a QuantumComputer backed by a QVM.
Expand All @@ -539,16 +543,17 @@ def _get_qvm_qc(
return QuantumComputer(
name=name,
qam=_get_qvm_or_pyqvm(
client_configuration=client_configuration,
qvm_type=qvm_type,
noise_model=noise_model,
quantum_processor=quantum_processor,
execution_timeout=execution_timeout,
qvm_client=qvm_client,
),
compiler=QVMCompiler(
quantum_processor=quantum_processor,
timeout=compiler_timeout,
client_configuration=client_configuration,
quilc_client=quilc_client,
),
)

Expand All @@ -562,6 +567,8 @@ def _get_qvm_with_topology(
qvm_type: str,
compiler_timeout: float,
execution_timeout: float,
quilc_client: Optional[QuilcClient] = None,
qvm_client: Optional[QVMClient] = None,
) -> QuantumComputer:
"""Construct a QVM with the provided topology.
Expand Down Expand Up @@ -593,6 +600,8 @@ def _get_qvm_with_topology(
noise_model=noise_model,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)


Expand All @@ -604,6 +613,8 @@ def _get_9q_square_qvm(
qvm_type: str,
compiler_timeout: float,
execution_timeout: float,
quilc_client: Optional[QuilcClient] = None,
qvm_client: Optional[QVMClient] = None,
) -> QuantumComputer:
"""
A nine-qubit 3x3 square lattice.
Expand All @@ -628,6 +639,8 @@ def _get_9q_square_qvm(
qvm_type=qvm_type,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)


Expand All @@ -640,6 +653,8 @@ def _get_unrestricted_qvm(
qvm_type: str,
compiler_timeout: float,
execution_timeout: float,
quilc_client: Optional[QuilcClient] = None,
qvm_client: Optional[QVMClient] = None,
) -> QuantumComputer:
"""
A qvm with a fully-connected topology.
Expand All @@ -664,6 +679,8 @@ def _get_unrestricted_qvm(
qvm_type=qvm_type,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)


Expand All @@ -676,6 +693,8 @@ def _get_qvm_based_on_real_quantum_processor(
qvm_type: str,
compiler_timeout: float,
execution_timeout: float,
quilc_client: Optional[QuilcClient] = None,
qvm_client: Optional[QVMClient] = None,
) -> QuantumComputer:
"""
A qvm with a based on a real quantum_processor.
Expand Down Expand Up @@ -704,6 +723,8 @@ def _get_qvm_based_on_real_quantum_processor(
qvm_type=qvm_type,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)


Expand All @@ -716,6 +737,8 @@ def get_qc(
execution_timeout: float = 30.0,
client_configuration: Optional[QCSClient] = None,
endpoint_id: Optional[str] = None,
quilc_client: Optional[QuilcClient] = None,
qvm_client: Optional[QVMClient] = None,
) -> QuantumComputer:
"""
Get a quantum computer.
Expand Down Expand Up @@ -814,6 +837,8 @@ def get_qc(
qvm_type=qvm_type,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)

# 3. Check for "9q-square" qvm
Expand All @@ -827,6 +852,8 @@ def get_qc(
qvm_type=qvm_type,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)

if noisy:
Expand All @@ -849,6 +876,8 @@ def get_qc(
qvm_type=qvm_type,
compiler_timeout=compiler_timeout,
execution_timeout=execution_timeout,
quilc_client=quilc_client,
qvm_client=qvm_client,
)
else:
qpu = QPU(
Expand All @@ -862,6 +891,7 @@ def get_qc(
quantum_processor=quantum_processor,
timeout=compiler_timeout,
client_configuration=client_configuration,
quilc_client=quilc_client,
)

return QuantumComputer(name=name, qam=qpu, compiler=compiler)
Expand Down
13 changes: 9 additions & 4 deletions pyquil/api/_qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
from typing import Any, Optional, Sequence, Tuple, Dict

import numpy as np

from qcs_sdk import QCSClient, qvm, ResultData, ExecutionData
from qcs_sdk.qvm import QVMOptions, QVMResultData
from qcs_sdk.qvm import QVMOptions, QVMResultData, QVMClient

from pyquil._version import pyquil_version
from pyquil.api import QAM, QuantumExecutable, QAMExecutionResult, MemoryMap
Expand Down Expand Up @@ -65,7 +66,7 @@ def __init__(
measurement_noise: Optional[Tuple[float, float, float]] = None,
random_seed: Optional[int] = None,
timeout: float = 10.0,
client_configuration: Optional[QCSClient] = None,
client: Optional[QVMClient] = None,
) -> None:
"""
A virtual machine that classically emulates the execution of Quil programs.
Expand Down Expand Up @@ -112,7 +113,11 @@ def __init__(
raise TypeError("random_seed should be None or a non-negative int")

self.timeout = timeout
self._client = client_configuration or QCSClient.load()

if client is None:
client = QVMClient.new_http(QCSClient.load().qvm_url)
self._client = client

self.connect()

def connect(self) -> None:
Expand Down Expand Up @@ -146,10 +151,10 @@ def execute(
trials,
addresses,
memory_map or {},
self._client,
self.measurement_noise,
self.gate_noise,
self.random_seed,
self._client,
options=QVMOptions(timeout_seconds=self.timeout),
)

Expand Down
9 changes: 6 additions & 3 deletions pyquil/api/_wavefunction_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(

self.timeout = timeout
self._client = client_configuration or QCSClient.load()
self._qvm_client = qvm.QVMClient.new_http(self._client.qvm_url)

def wavefunction(self, quil_program: Program, memory_map: Optional[MemoryMap] = None) -> Wavefunction:
"""
Expand Down Expand Up @@ -99,7 +100,7 @@ def wavefunction(self, quil_program: Program, memory_map: Optional[MemoryMap] =
self.random_seed,
)
wavefunction = bytes(
qvm.api.get_wavefunction(request, self._client, options=QVMOptions(timeout_seconds=self.timeout))
qvm.api.get_wavefunction(request, self._qvm_client, options=QVMOptions(timeout_seconds=self.timeout))
)
return Wavefunction.from_bit_packed_string(wavefunction)

Expand Down Expand Up @@ -146,7 +147,7 @@ def expectation(

request = qvm.api.ExpectationRequest(prep_prog.out(), [prog.out() for prog in progs])
expectations = qvm.api.measure_expectation(
request, self._client, options=QVMOptions(timeout_seconds=self.timeout)
request, self._qvm_client, options=QVMOptions(timeout_seconds=self.timeout)
)
bare_results = np.asarray(expectations)
results = coeffs * bare_results
Expand Down Expand Up @@ -202,7 +203,9 @@ def run_and_measure(
trials,
qubits,
)
measured_qubits = qvm.api.run_and_measure(request, options=QVMOptions(timeout_seconds=self.timeout))
measured_qubits = qvm.api.run_and_measure(
request, client=self._qvm_client, options=QVMOptions(timeout_seconds=self.timeout)
)
return np.asarray(measured_qubits)

@staticmethod
Expand Down
Loading

0 comments on commit 42c328a

Please sign in to comment.