diff --git a/CHANGELOG.md b/CHANGELOG.md index f587f2f71..aa6af01a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Changelog +## 4.1.1 (2023-11-15) + +### Fixes + +#### The ``execution_options`` property is now used for retrieving results if no overriding options were provided to the ``execute`` method. (#1694) + +## 4.1.1-rc.0 (2023-11-15) + +### Fixes + +#### The ``execution_options`` property is now used for retrieving results if no overriding options were provided to the ``execute`` method. (#1694) + +## 4.1.0 (2023-11-13) + +### Features + +#### update qcs-sdk-rust (#1683) + +### Fixes + +#### The `DefGate.matrix` property will no longer raise an exception when the matrix contains a mix of atomic and object types. (#1685) + +#### Instruction types no longer return a superclass instance when using `copy.deepcopy` (#1689) + +#### DefGate's no longer appear in the instructions list (#1688) + +## 4.1.0-rc.5 (2023-11-13) + +### Features + +#### update qcs-sdk-rust (#1683) + +### Fixes + +#### The `DefGate.matrix` property will no longer raise an exception when the matrix contains a mix of atomic and object types. (#1685) + +#### Instruction types no longer return a superclass instance when using `copy.deepcopy` (#1689) + +#### DefGate's no longer appear in the instructions list (#1688) + ## 4.1.0-rc.4 (2023-11-09) ### Features diff --git a/README.md b/README.md index 55afc34a0..63b68a13d 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ p += MEASURE(0, ro[0]) p += MEASURE(1, ro[1]) p.wrap_in_numshots_loop(10) -qvm.run(p).readout_data['ro'].tolist() +qvm.run(p).get_register_map()['ro'].tolist() ``` The output of the above program should look something like the following, diff --git a/docs/source/advanced_usage.rst b/docs/source/advanced_usage.rst index 5f9351cd6..937671607 100644 --- a/docs/source/advanced_usage.rst +++ b/docs/source/advanced_usage.rst @@ -69,7 +69,7 @@ Below is an example that demonstrates how to use pyQuil in a multithreading scen def run(program: Program): - return qc.run(qc.compile(program)).readout_data.get("ro") + return qc.run(qc.compile(program)).get_register_map().get("ro") programs = [ @@ -445,7 +445,7 @@ We can run this program a few times to see what we get in the readout register ` qc = get_qc("2q-qvm") branching_prog.wrap_in_numshots_loop(10) result = qc.run(branching_prog) - print(result.readout_data['test_register']) + print(result.get_register_map()['test_register']) .. testoutput:: control-flow :hide: diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index c1b4d022c..857540540 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -133,7 +133,7 @@ an entangled state between qubits 0 and 1 (that's what the "CNOT" gate does). Fi # run the program on a QVM qc = get_qc('9q-square-qvm') - result = qc.run(qc.compile(p)).readout_data.get("ro") + result = qc.run(qc.compile(p)).get_register_map().get("ro") print(result[0]) print(result[1]) @@ -182,7 +182,7 @@ the terminal windows where your servers are running, you should see output print with local_forest_runtime(): qvm = get_qc('9q-square-qvm') - bitstrings = qvm.run(qvm.compile(prog)).readout_data.get("ro") + bitstrings = qvm.run(qvm.compile(prog)).get_register_map().get("ro") In the following sections, we'll cover gates, program construction & execution, and go into detail about our Quantum Virtual Machine, our QPUs, noise models and more. Let's start with the :ref:`basics`. diff --git a/docs/source/introducing_v4.rst b/docs/source/introducing_v4.rst index 52a413b89..bf8bc61c3 100644 --- a/docs/source/introducing_v4.rst +++ b/docs/source/introducing_v4.rst @@ -106,7 +106,7 @@ you should use the ``get_raw_readout_data`` method to access the raw data and bu result = qc.run(exe) try: - matrix = result.readout_data + matrix = result.get_register_map() except RegisterMatrixConversionError: matrix = process_raw_data(result.get_raw_readout_data()) diff --git a/docs/source/noise.rst b/docs/source/noise.rst index 163416a70..0f11df13b 100644 --- a/docs/source/noise.rst +++ b/docs/source/noise.rst @@ -376,7 +376,7 @@ state decays to the :math:`\ket{0}` state. p.define_noisy_gate("I", [0], append_damping_to_gate(np.eye(2), damping_per_I)) p.wrap_in_numshots_loop(trials) qc.qam.random_seed = int(num_I) - res = qc.run(p).readout_data.get("ro") + res = qc.run(p).get_register_map().get("ro") results_damping.append([np.mean(res), np.std(res) / np.sqrt(trials)]) results_damping = np.array(results_damping) @@ -537,7 +537,7 @@ good starting point.** p.define_noisy_gate("CZ", [0, 1], corrupted_CZ) p.wrap_in_numshots_loop(trials) qc.qam.random_seed = jj - res = qc.run(p).readout_data.get("ro") + res = qc.run(p).get_register_map().get("ro") results.append(res) results = np.array(results) @@ -706,7 +706,7 @@ gate noise, respectively. MEASURE(0, ("ro", 0)), MEASURE(1, ("ro", 1)), ]) - bitstrings = qc.run(noisy).readout_data.get("ro") + bitstrings = qc.run(noisy).get_register_map().get("ro") # Expectation of Z0 and Z1 z0, z1 = 1 - 2*np.mean(bitstrings, axis=0) @@ -1002,7 +1002,7 @@ Example 1: Rabi sequence with noisy readout p.define_noisy_readout(0, p00=p00, p11=p00) ro = p.declare("ro", "BIT", 1) p.measure(0, ro[0]) - res = qc.run(p).readout_data.get("ro") + res = qc.run(p).get_register_map().get("ro") results_rabi[jj, kk] = np.sum(res) .. parsed-literal:: @@ -1149,7 +1149,7 @@ Pauli-Z moments that indicate the qubit correlations are corrupted (and correcte ) ghz_prog.wrap_in_numshots_loop(10000) print(ghz_prog) - results = qc.run(ghz_prog).readout_data.get("ro") + results = qc.run(ghz_prog).get_register_map().get("ro") .. testoutput:: readout-noise @@ -1167,7 +1167,7 @@ Pauli-Z moments that indicate the qubit correlations are corrupted (and correcte noisy_ghz = header + ghz_prog noisy_ghz.wrap_in_numshots_loop(10000) print(noisy_ghz) - noisy_results = qc.run(noisy_ghz).readout_data.get("ro") + noisy_results = qc.run(noisy_ghz).get_register_map().get("ro") .. testoutput:: readout-noise @@ -1374,9 +1374,9 @@ we should always measure ``1``. qc = get_qc("1q-qvm") print("Without Noise:") - print(qc.run(p).readout_data.get("ro")) + print(qc.run(p).get_register_map().get("ro")) print("With Noise:") - print(noisy_qc.run(p).readout_data.get("ro")) + print(noisy_qc.run(p).get_register_map().get("ro")) .. testoutput:: global-error :hide: diff --git a/docs/source/programs_and_gates.rst b/docs/source/programs_and_gates.rst index b8f65ac82..6891885f9 100644 --- a/docs/source/programs_and_gates.rst +++ b/docs/source/programs_and_gates.rst @@ -84,7 +84,7 @@ program on the Quantum Virtual Machine (QVM). We just have to add a few lines to qc = get_qc('1q-qvm') # You can make any 'nq-qvm' this way for any reasonable 'n' executable = qc.compile(p) result = qc.run(executable) - bitstrings = result.readout_data.get('ro') + bitstrings = result.get_register_map().get('ro') print(bitstrings) Congratulations! You just ran your program on the QVM. The returned value should be: @@ -310,7 +310,7 @@ filled in for, say, 200 values between :math:`0` and :math:`2\pi`. We demonstrat memory_map = {"theta": [theta]} # Get the results of the run with the value we want to execute with - bitstrings = qc.run(executable, memory_map=memory_map).readout_data.get("ro") + bitstrings = qc.run(executable, memory_map=memory_map).get_register_map().get("ro") # Store our results parametric_measurements.append(bitstrings) diff --git a/pyproject.toml b/pyproject.toml index d81ed48db..de8ba60ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyquil" -version = "4.1.0-rc.4" +version = "4.1.1" description = "A Python library for creating Quantum Instruction Language (Quil) programs." authors = ["Rigetti Computing "] readme = "README.md" diff --git a/pyquil/api/_qpu.py b/pyquil/api/_qpu.py index 0dee979e7..a4815fef5 100644 --- a/pyquil/api/_qpu.py +++ b/pyquil/api/_qpu.py @@ -188,16 +188,17 @@ def execute( memory_map = memory_map or {} patch_values = build_patch_values(executable.recalculation_table, memory_map) + effective_execution_options = execution_options or self.execution_options job_id = submit( program=executable.program, patch_values=patch_values, quantum_processor_id=self.quantum_processor_id, client=self._client_configuration, - execution_options=execution_options or self.execution_options, + execution_options=effective_execution_options, ) - return QPUExecuteResponse(_executable=executable, job_id=job_id, execution_options=execution_options) + return QPUExecuteResponse(_executable=executable, job_id=job_id, execution_options=effective_execution_options) def get_result(self, execute_response: QPUExecuteResponse) -> QAMExecutionResult: """ diff --git a/test/unit/test_qpu.py b/test/unit/test_qpu.py index 8857982f1..8f9063371 100644 --- a/test/unit/test_qpu.py +++ b/test/unit/test_qpu.py @@ -3,7 +3,7 @@ import numpy as np -from pyquil.api import ConnectionStrategy, ExecutionOptions, RegisterMatrixConversionError +from pyquil.api import ConnectionStrategy, ExecutionOptions, RegisterMatrixConversionError, ExecutionOptionsBuilder from pyquil.api._qpu import QPU from pyquil.api._abstract_compiler import EncryptedProgram from pyquil.quil import Program @@ -105,3 +105,77 @@ def test_qpu_execute_jagged_results( assert raw_readout_data.mappings == {"ro[0]": "q0", "ro[1]": "q1"} assert raw_readout_data.readout_values == {"q0": [1, 1], "q1": [1, 1, 1, 1]} + + +class TestQPUExecutionOptions: + @patch("pyquil.api._qpu.retrieve_results") + @patch("pyquil.api._qpu.submit") + def test_submit_with_class_options( + self, mock_submit: MagicMock, mock_retrieve_results: MagicMock, mock_encrypted_program: EncryptedProgram + ): + """ + Asserts that a ``QPU``'s execution_options property is used for submission, appears in the returned + ``QPUExecuteResponse``, and is used for retrieval of results when execution options are not provided to + ``QPU.execute``. + """ + qpu = QPU(quantum_processor_id="test") + execution_options_builder = ExecutionOptionsBuilder() + execution_options_builder.timeout_seconds = 10.0 + execution_options_builder.connection_strategy = ConnectionStrategy.endpoint_id("some-endpoint-id") + execution_options = execution_options_builder.build() + qpu.execution_options = execution_options + + mock_submit.return_value = "some-job-id" + execute_response = qpu.execute(mock_encrypted_program) + assert execute_response.execution_options == qpu.execution_options + + mock_retrieve_results.return_value = ExecutionResults( + { + "q0": ExecutionResult.from_register(Register.from_i32([1, 1])), + "q1": ExecutionResult.from_register(Register.from_i32([1, 1, 1, 1])), + } + ) + + qpu.get_result(execute_response) + + mock_retrieve_results.assert_called_once_with( + job_id="some-job-id", + quantum_processor_id="test", + client=qpu._client_configuration, + execution_options=qpu.execution_options, + ) + + @patch("pyquil.api._qpu.retrieve_results") + @patch("pyquil.api._qpu.submit") + def test_submit_with_options( + self, mock_submit: MagicMock, mock_retrieve_results: MagicMock, mock_encrypted_program: EncryptedProgram + ): + """ + Asserts that execution_options provided to ``QPU.execute`` are used for submission, appear in the returned + ``QPUExecuteResponse``, and are used for retrieval of results. + """ + qpu = QPU(quantum_processor_id="test") + + 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") + execution_options = execution_options_builder.build() + execute_response = qpu.execute(mock_encrypted_program, execution_options=execution_options) + assert execute_response.execution_options == execution_options + + mock_retrieve_results.return_value = ExecutionResults( + { + "q0": ExecutionResult.from_register(Register.from_i32([1, 1])), + "q1": ExecutionResult.from_register(Register.from_i32([1, 1, 1, 1])), + } + ) + + qpu.get_result(execute_response) + + mock_retrieve_results.assert_called_once_with( + job_id="some-job-id", + quantum_processor_id="test", + client=qpu._client_configuration, + execution_options=execution_options, + )