From d7c4f9573fc75c0e9131620b948515fa642c82c1 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Wed, 28 Feb 2024 14:25:09 -0800 Subject: [PATCH 1/8] feat: Add execute_with_memory_map_batch method for QPUs --- poetry.lock | 46 +++++++++++++++++++++++----------------------- pyproject.toml | 2 +- pyquil/api/_qpu.py | 41 +++++++++++++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/poetry.lock b/poetry.lock index 797af601f..dce0c52aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2385,32 +2385,32 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "qcs-sdk-python" -version = "0.17.0" +version = "0.17.1" description = "Python interface for the QCS Rust SDK" optional = false python-versions = "*" files = [ - {file = "qcs_sdk_python-0.17.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3fb4ca8f97d02b0d94f8955e22581c54e598e67698d320c0bb6cbfd069834ce0"}, - {file = "qcs_sdk_python-0.17.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:459dc2cca93d5c9487ef893cfe1404966d927c7d9556f2d09c52eba2f87eeafa"}, - {file = "qcs_sdk_python-0.17.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:90e7ad1ad929fea6e7f25fd7607da1fb909b7790604049cd6e8db0d51e76101a"}, - {file = "qcs_sdk_python-0.17.0-cp310-none-win_amd64.whl", hash = "sha256:631ea09e71d658d43a8d324f155e0a107a38bd13b6fbc6f7617f9ce159bdd8f5"}, - {file = "qcs_sdk_python-0.17.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4dffe02fd80764c41b06cf9839f78b123d836dadba8373c48f98085044641433"}, - {file = "qcs_sdk_python-0.17.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7fdb86da63da3fa7f83b6b96acbdc5bea65f31e99cd0bb497d6bbbe23713503c"}, - {file = "qcs_sdk_python-0.17.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1088cd9d3f6aab1fb9b1a17e04ca5cb598170e5bd1d8b1c3236704ea9f64dda"}, - {file = "qcs_sdk_python-0.17.0-cp311-none-win_amd64.whl", hash = "sha256:e22821c324e3529553e540261408b2700e701ce1b95733cedc466ce7988a2f0c"}, - {file = "qcs_sdk_python-0.17.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3f1606606b900ed188dea6823e388cc75e4555f743905c03abf25667f0ba39a2"}, - {file = "qcs_sdk_python-0.17.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:aae0635b017e43f627e296b8b943d815285cdb2bf5fbed2cbebdff292bae31c9"}, - {file = "qcs_sdk_python-0.17.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:489010ceaaf0192b2567477f4b79b9c3aac82fa2de84ccdd2d5916f8ab018d39"}, - {file = "qcs_sdk_python-0.17.0-cp312-none-win_amd64.whl", hash = "sha256:d886f7ed24d02d6a5d16fd53c4ee37a05dccff1db9ba2558285c9ab53af731f4"}, - {file = "qcs_sdk_python-0.17.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4d40da6d72e50877e239bb04a83e2647875bc1d21a6bd5160e6806df8d8e1470"}, - {file = "qcs_sdk_python-0.17.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:dddc04949909ca533e00425f9b06037f9880954028e9b66c83d59cbd3f5a77a9"}, - {file = "qcs_sdk_python-0.17.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:ea2815f34cef0c464c49c33621fe25ffafaed7ee99086512cd3c993c24016d54"}, - {file = "qcs_sdk_python-0.17.0-cp38-none-win_amd64.whl", hash = "sha256:109a3960f492d39e888c932a922971519f9fa941302d31e2e1e14d7d770c44f1"}, - {file = "qcs_sdk_python-0.17.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7fe28c34d7150b33534707e8783965115363d22f9c433e9849fd029f24706ea7"}, - {file = "qcs_sdk_python-0.17.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8eec185649a0e635a255ab49a62c7c5c94bccf85bb3ad886f3fe39b9b6e91f8c"}, - {file = "qcs_sdk_python-0.17.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:445bbc32086285fb1a7a2ac0bcc35e765c5c8df92909a89b1f107e4cb38127b5"}, - {file = "qcs_sdk_python-0.17.0-cp39-none-win_amd64.whl", hash = "sha256:2f5c1660297a471e4657ad0e41c142576528fbe62090692273ab7a5de98502a6"}, - {file = "qcs_sdk_python-0.17.0.tar.gz", hash = "sha256:9ee0dced1575b8b45d3e21b803fbca14310110257e2fb2d998e5bd4d564e8252"}, + {file = "qcs_sdk_python-0.17.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d0c5ad7b10fdc817e0d1b66853b6c90a1a69c474c65e76d11abb4577c79edf39"}, + {file = "qcs_sdk_python-0.17.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7ae49c6570a23478655e252be0d95ea42025950df42ea28114fa4cf0731687e3"}, + {file = "qcs_sdk_python-0.17.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0a29f2307769ebdfd9c205e89e75da8b1e61584ae99389b0bc99cc186c210089"}, + {file = "qcs_sdk_python-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:76947eacaecb44110837ebe8e735451541c6829cbf7b2882f86f02066030a9a5"}, + {file = "qcs_sdk_python-0.17.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0d7dfe7bb729c94b0d555c252bb8c2cd1786133ced457a670aa3b057f572c8c1"}, + {file = "qcs_sdk_python-0.17.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:09ac551a50057495712f0f462d4d6a7505abf3c29c78fa97100882de4028493b"}, + {file = "qcs_sdk_python-0.17.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:82112980f7c0ebc932aac63fdd47d7a287ccb008e92381f302796c419b8cc65e"}, + {file = "qcs_sdk_python-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:6124eafad081b536b10bbd3ff5dc418e23fcf8c5390858b25d15c86979ab3cf2"}, + {file = "qcs_sdk_python-0.17.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a8a0afda8746a13e49fec08376b44934db99e18952d9c09e54fbd758f0e2edb"}, + {file = "qcs_sdk_python-0.17.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c6fd542832ab09741793a7148b5d68898630e6f86eba0e599e42496162f7f9bb"}, + {file = "qcs_sdk_python-0.17.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:22152e265db8e6523b9be9c1816fdd4f656494a251ac8fbd4acedc8002ff19a0"}, + {file = "qcs_sdk_python-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:d59669d663ab2e08b08f75b248d105a79a7a8e067721aefc485a93b9507c3947"}, + {file = "qcs_sdk_python-0.17.1-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:df5a8a5b56d491fdbb48ce2b354cbe4096ff4aecefd5b66484d03c56d1a177a7"}, + {file = "qcs_sdk_python-0.17.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:8d1f97ea11c4a4282cc02141ee439b0d3c3c550337e76d4a6c80ca8cb68b3732"}, + {file = "qcs_sdk_python-0.17.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:2e8919f76d32329ebd05d69f332361f37cecf8429c173552c0f1e6d4c87d4f13"}, + {file = "qcs_sdk_python-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:a6d3aa110727560009ad670fb2c40df6b7136703cf5270d82e53b530d414cf3b"}, + {file = "qcs_sdk_python-0.17.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96cd89ea1f4d6cb2cb3c713900043f4fa127eae6daae671be752a6cfcb438761"}, + {file = "qcs_sdk_python-0.17.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:d730c18d9d90c6ca1506ced83856b0870fd5c0e792a392815670c62223d4bda1"}, + {file = "qcs_sdk_python-0.17.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:18132ce58244f987a8cfb0f5edb40580dfab1b432cdc3519403181fb379591a5"}, + {file = "qcs_sdk_python-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:47e12af249e52a9411343f2518adbab953f14655e66661154d9971d8ef6a4bb0"}, + {file = "qcs_sdk_python-0.17.1.tar.gz", hash = "sha256:0e36ef2562cbcec7cd09935ab650049e97ae10a3180d5df78894c3bf8c31b122"}, ] [package.dependencies] @@ -3288,4 +3288,4 @@ latex = ["ipython"] [metadata] lock-version = "2.0" python-versions = "^3.8,<=3.12" -content-hash = "dbd592dca3609c61903f6dcf4607a996d293614ff77e750c6cdb4c4ce8010e8b" +content-hash = "30cfab0707ba9f11a7a568bb0d026809911e4826287576886c194ef6b148c2b3" diff --git a/pyproject.toml b/pyproject.toml index ffb4a3704..7c0f2af9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ lark = "^0.11.1" rpcq = "^3.10.0" networkx = ">=2.5" importlib-metadata = { version = ">=3.7.3,<5", python = "<3.8" } -qcs-sdk-python = "0.17.0" +qcs-sdk-python = "0.17.1" tenacity = "^8.2.2" types-python-dateutil = "^2.8.19" types-retry = "^0.9.9" diff --git a/pyquil/api/_qpu.py b/pyquil/api/_qpu.py index 81b17f51c..da2dd6453 100644 --- a/pyquil/api/_qpu.py +++ b/pyquil/api/_qpu.py @@ -16,7 +16,7 @@ from dataclasses import dataclass from collections import defaultdict from datetime import timedelta -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Optional, Union, List import numpy as np from numpy.typing import NDArray @@ -31,13 +31,13 @@ ) from qcs_sdk import QCSClient, ResultData, ExecutionData from qcs_sdk.qpu.api import ( - submit, retrieve_results, cancel_job, ConnectionStrategy, ExecutionResult, ExecutionOptions, ExecutionOptionsBuilder, + submit_with_parameter_batch, ) from qcs_sdk.qpu.rewrite_arithmetic import build_patch_values @@ -177,6 +177,28 @@ def execute( to configure how the job is submitted and retrieved from the QPU. If unset, an appropriate default will be used. """ + memory_map = memory_map or {} + responses = self.execute_with_memory_map_batch(executable, [memory_map], execution_options) + assert len(responses) == 1, "Request to execute job with a single memory map returned multiple responses" + return responses[0] + + def execute_with_memory_map_batch( + self, + executable: QuantumExecutable, + memory_maps: List[MemoryMap], + execution_options: Optional[ExecutionOptions] = None, + ) -> List[QPUExecuteResponse]: + """ + Execute a compiled program on a QPU with multiple sets of `memory_maps`. + + See the documentation of `qcs_sdk.qpu.api.submit_with_parameter_batch` for more information. + + :param program: The `EncryptedProgram` to execute. + :param memory_maps: A list containing one or more mappings of symbols to their desired values. + :param execution_options: The ``ExecutionOptions`` to use. + + :returns: A list of responses with a length and order corresponding to the memory_maps given. + """ executable = executable.copy() assert isinstance( @@ -187,11 +209,14 @@ def execute( executable.ro_sources is not None ), "To run on a QPU, a program must include ``MEASURE``, ``CAPTURE``, and/or ``RAW-CAPTURE`` instructions" - memory_map = memory_map or {} - patch_values = build_patch_values(executable.recalculation_table, memory_map) + patch_values = [] + for memory_map in memory_maps: + memory_map = memory_map or {} + patch_values.append(build_patch_values(executable.recalculation_table, memory_map)) + effective_execution_options = execution_options or self.execution_options - job_id = submit( + job_ids = submit_with_parameter_batch( program=executable.program, patch_values=patch_values, quantum_processor_id=self.quantum_processor_id, @@ -199,7 +224,11 @@ def execute( execution_options=effective_execution_options, ) - return QPUExecuteResponse(_executable=executable, job_id=job_id, execution_options=effective_execution_options) + responses = [] + for job_id in job_ids: + responses.append( + QPUExecuteResponse(_executable=executable, job_id=job_id, execution_options=effective_execution_options) + ) def cancel(self, execute_response: QPUExecuteResponse) -> None: """ From 0847a11a7cdbe2a2f2863a10f1d26f706df4b079 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Wed, 28 Feb 2024 14:31:11 -0800 Subject: [PATCH 2/8] return the responses --- pyquil/api/_qpu.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyquil/api/_qpu.py b/pyquil/api/_qpu.py index da2dd6453..f5903e5a5 100644 --- a/pyquil/api/_qpu.py +++ b/pyquil/api/_qpu.py @@ -230,6 +230,8 @@ def execute_with_memory_map_batch( QPUExecuteResponse(_executable=executable, job_id=job_id, execution_options=effective_execution_options) ) + return responses + def cancel(self, execute_response: QPUExecuteResponse) -> None: """ Cancel a job that has yet to begin executing. From 3b83d02689678d0f76e3c4e5998a208f343e8c0e Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Wed, 28 Feb 2024 14:56:01 -0800 Subject: [PATCH 3/8] update unit tests --- test/unit/test_qpu.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/unit/test_qpu.py b/test/unit/test_qpu.py index de92ccf7b..8fd8958b9 100644 --- a/test/unit/test_qpu.py +++ b/test/unit/test_qpu.py @@ -58,13 +58,13 @@ def test_provided_execution_options(): @patch("pyquil.api._qpu.retrieve_results") -@patch("pyquil.api._qpu.submit") +@patch("pyquil.api._qpu.submit_with_parameter_batch") def test_qpu_execute( mock_submit: MagicMock, mock_retrieve_results: MagicMock, mock_encrypted_program: EncryptedProgram ): qpu = QPU(quantum_processor_id="test") - mock_submit.return_value = "some-job-id" + mock_submit.return_value = ["some-job-id"] execute_response = qpu.execute(mock_encrypted_program) mock_retrieve_results.return_value = ExecutionResults( @@ -91,13 +91,13 @@ def test_qpu_execute( @patch("pyquil.api._qpu.retrieve_results") -@patch("pyquil.api._qpu.submit") +@patch("pyquil.api._qpu.submit_with_parameter_batch") def test_qpu_execute_jagged_results( mock_submit: MagicMock, mock_retrieve_results: MagicMock, mock_encrypted_program: EncryptedProgram ): qpu = QPU(quantum_processor_id="test") - mock_submit.return_value = "some-job-id" + mock_submit.return_value = ["some-job-id"] execute_response = qpu.execute(mock_encrypted_program) mock_retrieve_results.return_value = ExecutionResults( @@ -130,7 +130,7 @@ def test_qpu_execute_jagged_results( class TestQPUExecutionOptions: @patch("pyquil.api._qpu.retrieve_results") - @patch("pyquil.api._qpu.submit") + @patch("pyquil.api._qpu.submit_with_parameter_batch") def test_submit_with_class_options( self, mock_submit: MagicMock, mock_retrieve_results: MagicMock, mock_encrypted_program: EncryptedProgram ): @@ -146,7 +146,7 @@ def test_submit_with_class_options( execution_options = execution_options_builder.build() qpu.execution_options = execution_options - mock_submit.return_value = "some-job-id" + mock_submit.return_value = ["some-job-id"] execute_response = qpu.execute(mock_encrypted_program) assert execute_response.execution_options == qpu.execution_options @@ -168,7 +168,7 @@ def test_submit_with_class_options( ) @patch("pyquil.api._qpu.retrieve_results") - @patch("pyquil.api._qpu.submit") + @patch("pyquil.api._qpu.submit_with_parameter_batch") def test_submit_with_options( self, mock_submit: MagicMock, mock_retrieve_results: MagicMock, mock_encrypted_program: EncryptedProgram ): @@ -178,7 +178,7 @@ def test_submit_with_options( """ qpu = QPU(quantum_processor_id="test") - mock_submit.return_value = "some-job-id" + mock_submit.return_value = ["some-job-id"] execution_options_builder = ExecutionOptionsBuilder() execution_options_builder.timeout_seconds = 10.0 execution_options_builder.connection_strategy = ConnectionStrategy.endpoint_id("some-endpoint-id") From 5a9da6d52d7551de26de5aee5090336b0d367579 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Thu, 29 Feb 2024 15:44:47 -0800 Subject: [PATCH 4/8] add to interface and update docs --- docs/source/programs_and_gates.rst | 22 +++++++++++----------- pyquil/api/_qam.py | 19 ++++++++++++++++++- pyquil/api/_qpu.py | 4 ++-- pyquil/api/_qvm.py | 13 ++++++++++++- pyquil/pyqvm.py | 9 ++++++++- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/docs/source/programs_and_gates.rst b/docs/source/programs_and_gates.rst index 70dde8be8..8ec73e19b 100644 --- a/docs/source/programs_and_gates.rst +++ b/docs/source/programs_and_gates.rst @@ -361,21 +361,21 @@ filled in for, say, 200 values between :math:`0` and :math:`2\pi`. We demonstrat .. testcode:: parametric - # Somewhere to store each list of results - parametric_measurements = [] + # Generate a memory map for each set of parameters we want to execute with + memory_maps = [{"theta": [theta] for theta in np.linspace(0, 2 * np.pi, 200)} - for theta in np.linspace(0, 2 * np.pi, 200): - # Set the desired parameter value in executable memory - memory_map = {"theta": [theta]} + # Begin batch execution of the program using each set of parameters. + # This returns a list of references to each job, the length and order of which correspond to the memory maps we + # pass in. + handles = qc.execute_with_memory_map_batch(executable, memory_maps) - # Get the results of the run with the value we want to execute with - bitstrings = qc.run(executable, memory_map=memory_map).get_register_map().get("ro") + # Use the handles to gather the results + parametric_measurements = [qc.get_result(handles) for handle in handles] - # Store our results - parametric_measurements.append(bitstrings) +.. note:: -In the example here, if you called ``qc.run(executable)`` and didn't specify ``'theta'``, the program would apply -``RZ(0, qubit)`` for every execution. + :py:meth:`~QAM.run` and :py:meth:`~QAM.execute` both support executing a program a single memory map. We chose + :py:meth:`~QAM.batch_execute_with_memory_map` for this example since we had multiple sets of parameters to run. .. note:: diff --git a/pyquil/api/_qam.py b/pyquil/api/_qam.py index 02ebe2c8a..0986acd59 100644 --- a/pyquil/api/_qam.py +++ b/pyquil/api/_qam.py @@ -15,7 +15,7 @@ ############################################################################## from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Any, Generic, Mapping, Optional, TypeVar, Sequence, Union, Dict +from typing import Any, Generic, Mapping, Optional, TypeVar, Sequence, Union, Dict, List, Iterable from datetime import timedelta from deprecated import deprecated @@ -144,6 +144,23 @@ def execute( region for the run. """ + @abstractmethod + def execute_with_memory_map_batch( + self, + executable: QuantumExecutable, + memory_maps: Iterable[MemoryMap], + **kwargs: Any, + ) -> List[T]: + """ + Execute a QuantumExecutable with one or more memory_maps, returning a handles to be used to retrieve results. + + How these programs are batched and executed is determined by the executor. See their respective documentation + for details. + + Returns a list of ``QAMExecutionResult``, which can be used to fetch + results in ``QAM#get_result``. + """ + @abstractmethod def get_result(self, execute_response: T) -> QAMExecutionResult: """ diff --git a/pyquil/api/_qpu.py b/pyquil/api/_qpu.py index f5903e5a5..b179fc9ba 100644 --- a/pyquil/api/_qpu.py +++ b/pyquil/api/_qpu.py @@ -16,7 +16,7 @@ from dataclasses import dataclass from collections import defaultdict from datetime import timedelta -from typing import Any, Dict, Optional, Union, List +from typing import Any, Dict, Optional, Union, List, Iterable import numpy as np from numpy.typing import NDArray @@ -185,7 +185,7 @@ def execute( def execute_with_memory_map_batch( self, executable: QuantumExecutable, - memory_maps: List[MemoryMap], + memory_maps: Iterable[MemoryMap], execution_options: Optional[ExecutionOptions] = None, ) -> List[QPUExecuteResponse]: """ diff --git a/pyquil/api/_qvm.py b/pyquil/api/_qvm.py index 2dc3de2e6..02ddcd950 100644 --- a/pyquil/api/_qvm.py +++ b/pyquil/api/_qvm.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################## from dataclasses import dataclass -from typing import Any, Optional, Sequence, Tuple, Dict +from typing import Any, Optional, Sequence, Tuple, Dict, List, Iterable import numpy as np @@ -127,6 +127,17 @@ def connect(self) -> None: except ConnectionError: raise QVMNotRunning(f"No QVM server running at {self._client.qvm_url}") from ConnectionError + def execute_with_memory_map_batch( + self, executable: QuantumExecutable, memory_maps: Iterable[MemoryMap], **__: Any + ) -> List[QVMExecuteResponse]: + """ + Executes a single program on the QVM with multiple memory maps. + + This method is a convenience wrapper around QVM#execute and isn't more efficient than making multiple seperate + requests to the QVM. + """ + return [self.execute(executable, memory_map) for memory_map in memory_maps] + def execute( self, executable: QuantumExecutable, diff --git a/pyquil/pyqvm.py b/pyquil/pyqvm.py index 9dfcd1657..dcb970284 100644 --- a/pyquil/pyqvm.py +++ b/pyquil/pyqvm.py @@ -15,7 +15,7 @@ ############################################################################## import logging from abc import ABC, abstractmethod -from typing import Dict, List, Optional, Sequence, Type, Union, Any +from typing import Dict, List, Optional, Sequence, Type, Union, Any, Iterable import numpy as np from numpy.random.mtrand import RandomState @@ -221,6 +221,13 @@ def _extract_defined_gates(self) -> None: raise NotImplementedError("PyQVM does not support DEFGATE ... AS MATRIX | PAULI-SUM.") self.defined_gates[dg.name] = dg.matrix + def execute_with_memory_map_batch( + self, executable: QuantumExecutable, memory_maps: Iterable[MemoryMap], **__: Any + ) -> List["PyQVM"]: + raise NotImplementedError( + "PyQVM does not support batch execution as the state of the instance is reset at the start of each execute." + ) + def execute(self, executable: QuantumExecutable, memory_map: Optional[MemoryMap] = None, **__: Any) -> "PyQVM": """ Execute a program on the PyQVM. Note that the state of the instance is reset on each From 713ad11e9f8d646455f45f67f7424e52660262d5 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Fri, 1 Mar 2024 10:06:01 -0800 Subject: [PATCH 5/8] fix qpu signature --- pyquil/api/_qpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyquil/api/_qpu.py b/pyquil/api/_qpu.py index b179fc9ba..1607e963d 100644 --- a/pyquil/api/_qpu.py +++ b/pyquil/api/_qpu.py @@ -187,6 +187,7 @@ def execute_with_memory_map_batch( executable: QuantumExecutable, memory_maps: Iterable[MemoryMap], execution_options: Optional[ExecutionOptions] = None, + **__: Any, ) -> List[QPUExecuteResponse]: """ Execute a compiled program on a QPU with multiple sets of `memory_maps`. From c5267668ab44e73129670f4a7c0eef3ca4c6c0cb Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Tue, 5 Mar 2024 14:29:36 -0800 Subject: [PATCH 6/8] Update pyquil/api/_qam.py Co-authored-by: Kalan <22137047+kalzoo@users.noreply.github.com> --- pyquil/api/_qam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyquil/api/_qam.py b/pyquil/api/_qam.py index 0986acd59..74c346ebc 100644 --- a/pyquil/api/_qam.py +++ b/pyquil/api/_qam.py @@ -152,7 +152,7 @@ def execute_with_memory_map_batch( **kwargs: Any, ) -> List[T]: """ - Execute a QuantumExecutable with one or more memory_maps, returning a handles to be used to retrieve results. + Execute a QuantumExecutable with one or more memory_maps, returning handles to be used to retrieve results. How these programs are batched and executed is determined by the executor. See their respective documentation for details. From 72fccca711ef37fcdde5dd285a4df36ab1839550 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Wed, 6 Mar 2024 09:56:13 -0800 Subject: [PATCH 7/8] fix comprehension --- docs/source/programs_and_gates.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/programs_and_gates.rst b/docs/source/programs_and_gates.rst index 8ec73e19b..76cf1f47f 100644 --- a/docs/source/programs_and_gates.rst +++ b/docs/source/programs_and_gates.rst @@ -362,7 +362,7 @@ filled in for, say, 200 values between :math:`0` and :math:`2\pi`. We demonstrat .. testcode:: parametric # Generate a memory map for each set of parameters we want to execute with - memory_maps = [{"theta": [theta] for theta in np.linspace(0, 2 * np.pi, 200)} + memory_maps = [{"theta": [theta] for theta in np.linspace(0, 2 * np.pi, 200)}] # Begin batch execution of the program using each set of parameters. # This returns a list of references to each job, the length and order of which correspond to the memory maps we From 09f6ef499f514c796bd3ba2c5f9daa58c1c47289 Mon Sep 17 00:00:00 2001 From: Marquess Valdez Date: Fri, 8 Mar 2024 09:52:57 -0800 Subject: [PATCH 8/8] bubble up the API to QuantumComputer --- docs/source/programs_and_gates.rst | 9 +++------ pyquil/api/_qam.py | 3 +-- pyquil/api/_quantum_computer.py | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/source/programs_and_gates.rst b/docs/source/programs_and_gates.rst index 76cf1f47f..e4a15b503 100644 --- a/docs/source/programs_and_gates.rst +++ b/docs/source/programs_and_gates.rst @@ -364,13 +364,10 @@ filled in for, say, 200 values between :math:`0` and :math:`2\pi`. We demonstrat # Generate a memory map for each set of parameters we want to execute with memory_maps = [{"theta": [theta] for theta in np.linspace(0, 2 * np.pi, 200)}] - # Begin batch execution of the program using each set of parameters. - # This returns a list of references to each job, the length and order of which correspond to the memory maps we + # Batch execute of the program using each set of parameters. + # This returns a list of results for each execution, the length and order of which correspond to the memory maps we # pass in. - handles = qc.execute_with_memory_map_batch(executable, memory_maps) - - # Use the handles to gather the results - parametric_measurements = [qc.get_result(handles) for handle in handles] + parametric_measurements = qc.run_with_memory_map_batch(executable, memory_maps) .. note:: diff --git a/pyquil/api/_qam.py b/pyquil/api/_qam.py index 74c346ebc..51974839c 100644 --- a/pyquil/api/_qam.py +++ b/pyquil/api/_qam.py @@ -157,8 +157,7 @@ def execute_with_memory_map_batch( How these programs are batched and executed is determined by the executor. See their respective documentation for details. - Returns a list of ``QAMExecutionResult``, which can be used to fetch - results in ``QAM#get_result``. + Returns a list of handles that can be used to fetch results with ``QAM#get_result``. """ @abstractmethod diff --git a/pyquil/api/_quantum_computer.py b/pyquil/api/_quantum_computer.py index 7a9be95c7..bf52ed1c8 100644 --- a/pyquil/api/_quantum_computer.py +++ b/pyquil/api/_quantum_computer.py @@ -24,6 +24,7 @@ Any, Tuple, Iterator, + Iterable, Optional, Set, Union, @@ -141,6 +142,22 @@ def run( """ return self.qam.run(executable, memory_map, **kwargs) + def run_with_memory_map_batch( + self, executable: QuantumExecutable, memory_maps: Iterable[MemoryMap], **kwargs: Any + ) -> List[QAMExecutionResult]: + """ + Run a QuantumExecutable with one or more memory_maps, returning a list of results corresponding to the length + and order of the given MemoryMaps. + + How these programs are batched and executed is determined by the executor. See their respective documentation + for details. + + Returns a list of ``QAMExecutionResult``, which can be used to fetch + results in ``QAM#get_result``. + """ + handles = self.qam.execute_with_memory_map_batch(executable, memory_maps, **kwargs) + return [self.qam.get_result(handle) for handle in handles] + def calibrate(self, experiment: Experiment) -> List[ExperimentResult]: """ Perform readout calibration on the various multi-qubit observables involved in the provided