Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: remove qcs-api-client dependency #1550

Merged
merged 14 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ install:
.PHONY: test
test:
poetry install --extras latex
pytest -n auto -v --runslow --cov=pyquil test/unit
pytest -v --runslow --cov=pyquil test/unit

.PHONY: test-fast
test-fast:
poetry install --extras latex
pytest -n auto -v --cov=pyquil test/unit
pytest -v --cov=pyquil test/unit

.PHONY: e2e
e2e:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pyQuil load its settings and secrets from specific locations. By default, config

Additionally, you can override whichever QVM and quilc URLs are loaded from ``settings.toml``
(``profiles.<profile>.applications.pyquil.qvm_url`` and ``profiles.<profile>.applications.pyquil.quilc_url`` fields)
by setting the ``QCS_SETTINGS_APPLICATIONS_PYQUIL_QVM_URL`` and/or ``QCS_SETTINGS_APPLICATIONS_PYQUIL_QUILC_URL``
by setting the ``QCS_SETTINGS_APPLICATIONS_PYQUIL_QVM_URL`` and/or ``QCS_SETTINGS_APPLICATIONS_QUILC_URL``
environment variables. If these URLs are missing from ``settings.toml`` and are not set by environment variables,
the following defaults will be used (as they correspond to the default behavior of the QVM and quilc when running
locally):
Expand Down
2 changes: 2 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,8 @@
def builder_inited_handler(app):
subprocess.call(
[
"python",
"-m",
"pandoc",
"--from=markdown",
"--to=rst",
Expand Down
1 change: 0 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ to get started.
troubleshooting
exercises
migration
changes

.. toctree::
:maxdepth: 2
Expand Down
2 changes: 2 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ allow_redefinition = True

no_implicit_reexport = False

plugins = numpy.typing.mypy_plugin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, this is great.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't seem to do anything though :/


# Ignore errors in all parser-related files
[mypy-pyquil._parser.*]
ignore_errors = True
Expand Down
1,358 changes: 713 additions & 645 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ numpy = "^1.21"
scipy = "^1.7.3"
lark = "^0.11.1"
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.4.2"
qcs-api-client = ">=0.21.0,<0.22.0"
qcs-sdk-python = "0.5.0rc.17"
retry = "^0.9.2"
types-python-dateutil = "^2.8.19"
types-retry = "^0.9.9"
Expand All @@ -40,6 +40,7 @@ Sphinx = { version = "^4.0.2", optional = true }
sphinx-rtd-theme = { version = "^0.5.2", optional = true }
nbsphinx = { version = "^0.8.6", optional = true }
recommonmark = { version = "^0.7.1", optional = true }
pandoc = { version = "^2.3", optional = true }

[tool.poetry.dev-dependencies]
black = "^22.8.0"
Expand All @@ -58,7 +59,7 @@ mock = { version = "^4.0", python = "<3.8" }

[tool.poetry.extras]
latex = ["ipython"]
docs = ["Sphinx", "sphinx-rtd-theme", "nbsphinx", "recommonmark"]
docs = ["Sphinx", "sphinx-rtd-theme", "nbsphinx", "recommonmark", "pandoc"]

[tool.black]
line-length = 120
Expand Down
4 changes: 2 additions & 2 deletions pyquil/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"local_forest_runtime",
"QAM",
"QAMExecutionResult",
"QCSClientConfiguration",
"QCSClient",
"QCSQuantumProcessor",
"QPU",
"QPUCompiler",
Expand All @@ -37,7 +37,7 @@
"WavefunctionSimulator",
]

from qcs_api_client.client import QCSClientConfiguration
from qcs_sdk import QCSClient

from pyquil.api._benchmark import BenchmarkConnection
from pyquil.api._compiler import QVMCompiler, QPUCompiler, QuantumExecutable, EncryptedProgram, AbstractCompiler
Expand Down
46 changes: 16 additions & 30 deletions pyquil/api/_abstract_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
from dataclasses import dataclass
import dataclasses
from typing import Any, Dict, List, Optional, Sequence, Union
import asyncio
import json

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

from pyquil._memory import Memory
from pyquil._version import pyquil_version
Expand All @@ -31,7 +31,6 @@
from pyquil.quil import Program
from pyquil.quilatom import MemoryReference
from pyquil.quilbase import Gate
from qcs_api_client.client import QCSClientConfiguration
from rpcq.messages import ParameterAref, ParameterSpec


Expand Down Expand Up @@ -87,27 +86,21 @@ def write_memory(
class AbstractCompiler(ABC):
"""The abstract interface for a compiler."""

_event_loop: asyncio.AbstractEventLoop

def __init__(
self,
*,
quantum_processor: AbstractQuantumProcessor,
timeout: float,
client_configuration: Optional[QCSClientConfiguration] = None,
event_loop: Optional[asyncio.AbstractEventLoop] = None,
client_configuration: Optional[QCSClient] = None,
) -> None:
self.quantum_processor = quantum_processor
self._timeout = timeout

self._client_configuration = client_configuration or QCSClientConfiguration.load()

if event_loop is None:
event_loop = asyncio.get_event_loop()
self._event_loop = event_loop
self._client_configuration = client_configuration or QCSClient.load()

self._compiler_client = CompilerClient(
client_configuration=self._client_configuration, request_timeout=timeout, event_loop=self._event_loop
client_configuration=self._client_configuration,
request_timeout=timeout,
)

self._connect()
Expand All @@ -125,23 +118,16 @@ def quil_to_native_quil(self, program: Program, *, protoquil: Optional[bool] = N
Convert a Quil program into native Quil, which is supported for execution on a QPU.
"""

# This is a work-around needed because calling `qcs_sdk.compile` happens _before_
# the event loop is available. Wrapping it in a Python async function ensures that
# the event loop is available. This is a limitation of pyo3:
# https://pyo3.rs/v0.17.1/ecosystem/async-await.html#a-note-about-asynciorun
async def _compile(*args, **kwargs) -> str: # type: ignore
return await qcs_sdk.compile(*args, **kwargs)

# TODO This ISA isn't always going to be available. Specifically, if the quantum processor is
# a QVM-type processor, then `quantum_processor` will have a CompilerISA, not a QCSISA.
# This will have to be addressed as part of this issue: https://github.com/rigetti/pyquil/issues/1496
target_device = compiler_isa_to_target_quantum_processor(self.quantum_processor.to_compiler_isa())
native_quil = self._event_loop.run_until_complete(
_compile(
program.out(calibrations=False),
json.dumps(target_device.asdict(), indent=2), # type: ignore
timeout=self._compiler_client.timeout,
)
# convert the pyquil ``TargetDevice`` to the qcs_sdk ``TargetDevice``
compiler_isa = self.quantum_processor.to_compiler_isa()
target_device_json = json.dumps(compiler_isa_to_target_quantum_processor(compiler_isa).asdict()) # type: ignore
target_device = TargetDevice.from_json(target_device_json)

native_quil = compile_program(
quil=program.out(calibrations=False),
target=target_device,
client=self._client_configuration,
options=CompilerOpts(protoquil=protoquil, timeout=self._compiler_client.timeout),
)

native_program = Program(native_quil)
Expand Down
6 changes: 3 additions & 3 deletions pyquil/api/_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
##############################################################################
from typing import List, Optional, Sequence, cast

from qcs_api_client.client import QCSClientConfiguration
from qcs_sdk import QCSClient

from pyquil.api._abstract_compiler import AbstractBenchmarker
from pyquil.api._compiler_client import (
Expand All @@ -34,7 +34,7 @@ class BenchmarkConnection(AbstractBenchmarker):
Represents a connection to a server that generates benchmarking data.
"""

def __init__(self, *, timeout: float = 10.0, client_configuration: Optional[QCSClientConfiguration] = None):
def __init__(self, *, timeout: float = 10.0, client_configuration: Optional[QCSClient] = None):
"""
Client to communicate with the benchmarking data endpoint.

Expand All @@ -43,7 +43,7 @@ def __init__(self, *, timeout: float = 10.0, client_configuration: Optional[QCSC
"""

self._compiler_client = CompilerClient(
client_configuration=client_configuration or QCSClientConfiguration.load(),
client_configuration=client_configuration or QCSClient.load(),
request_timeout=timeout,
)

Expand Down
71 changes: 24 additions & 47 deletions pyquil/api/_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##############################################################################
from contextlib import contextmanager
from typing import Dict, Optional, Iterator
import asyncio
from typing import Dict, Optional

import httpx
from pyquil.parser import parse_program
from pyquil.quilatom import MemoryReference
from pyquil.quilbase import Declare
import qcs_sdk
from qcs_api_client.client import QCSClientConfiguration
from qcs_api_client.operations.sync import (
get_quilt_calibrations,
)
from qcs_sdk import QCSClient
from qcs_sdk.qpu.rewrite_arithmetic import rewrite_arithmetic
from qcs_sdk.qpu.translation import get_quilt_calibrations, translate
from rpcq.messages import ParameterSpec

from pyquil.api._abstract_compiler import AbstractCompiler, QuantumExecutable, EncryptedProgram
from pyquil.api._qcs_client import qcs_client
from pyquil.api._abstract_compiler import AbstractCompiler, EncryptedProgram, QuantumExecutable
from pyquil.parser import parse_program
from pyquil.quantum_processor import AbstractQuantumProcessor
from pyquil.quil import Program
from pyquil.quilatom import MemoryReference
from pyquil.quilbase import Declare


class QPUCompilerNotRunning(Exception):
Expand Down Expand Up @@ -75,8 +69,7 @@ def __init__(
quantum_processor_id: str,
quantum_processor: AbstractQuantumProcessor,
timeout: float = 10.0,
client_configuration: Optional[QCSClientConfiguration] = None,
event_loop: Optional[asyncio.AbstractEventLoop] = None,
client_configuration: Optional[QCSClient] = None,
) -> None:
"""
Instantiate a new QPU compiler client.
Expand All @@ -90,7 +83,6 @@ def __init__(
quantum_processor=quantum_processor,
timeout=timeout,
client_configuration=client_configuration,
event_loop=event_loop,
)

self.quantum_processor_id = quantum_processor_id
Expand All @@ -100,34 +92,28 @@ def native_quil_to_executable(self, nq_program: Program) -> QuantumExecutable:
"""
Convert a native Quil program into an executable binary which can be executed by a QPU.
"""
rewrite_response = qcs_sdk.rewrite_arithmetic(nq_program.out())

# This is a work-around needed because calling `qcs_sdk.translate` happens _before_
# the event loop is available. Wrapping it in a Python async function ensures that
# the event loop is available. This is a limitation of pyo3:
# https://pyo3.rs/v0.17.1/ecosystem/async-await.html#a-note-about-asynciorun
async def _translate(*args): # type: ignore
return await qcs_sdk.translate(*args)

translated_program = self._event_loop.run_until_complete(
_translate( # type: ignore
rewrite_response["program"],
nq_program.num_shots,
self.quantum_processor_id,
)
rewrite_response = rewrite_arithmetic(nq_program.out())

translated_program = translate(
native_quil=rewrite_response.program,
num_shots=nq_program.num_shots,
quantum_processor_id=self.quantum_processor_id,
)

ro_sources = translated_program.ro_sources or {}

return EncryptedProgram(
program=translated_program["program"],
program=translated_program.program,
memory_descriptors=_collect_memory_descriptors(nq_program),
ro_sources={parse_mref(mref): source for mref, source in translated_program["ro_sources"].items() or []},
recalculation_table=rewrite_response["recalculation_table"],
ro_sources={parse_mref(mref): source for mref, source in ro_sources.items() or []},
recalculation_table=rewrite_response.recalculation_table,
_memory=nq_program._memory.copy(),
)

def _fetch_calibration_program(self) -> Program:
with self._qcs_client() as qcs_client: # type: httpx.Client
response = get_quilt_calibrations(client=qcs_client, quantum_processor_id=self.quantum_processor_id).parsed
response = get_quilt_calibrations(
quantum_processor_id=self.quantum_processor_id,
)
return parse_program(response.quilt)

def get_calibration_program(self, force_refresh: bool = False) -> Program:
Expand Down Expand Up @@ -161,13 +147,6 @@ def reset(self) -> None:
super().reset()
self._calibration_program = None

@contextmanager
def _qcs_client(self) -> Iterator[httpx.Client]:
with qcs_client(
client_configuration=self._client_configuration, request_timeout=self._timeout
) as client: # type: httpx.Client
yield client


class QVMCompiler(AbstractCompiler):
"""
Expand All @@ -179,8 +158,7 @@ def __init__(
*,
quantum_processor: AbstractQuantumProcessor,
timeout: float = 10.0,
client_configuration: Optional[QCSClientConfiguration] = None,
event_loop: Optional[asyncio.AbstractEventLoop] = None,
client_configuration: Optional[QCSClient] = None,
) -> None:
"""
Client to communicate with compiler.
Expand All @@ -193,7 +171,6 @@ def __init__(
quantum_processor=quantum_processor,
timeout=timeout,
client_configuration=client_configuration,
event_loop=event_loop,
)

def native_quil_to_executable(self, nq_program: Program) -> QuantumExecutable:
Expand Down
Loading