From 3803744b47a3938d1af22cfd516a7eaee5112603 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 1 May 2024 15:38:51 -0400 Subject: [PATCH 1/2] fix: Force StateVector and DensityMatrix values to be ndarrays and test --- src/braket/simulator_v2/simulator.py | 37 +++++++++++++++++++ .../test_density_matrix_simulator.py | 33 ++++++++++++++++- .../test_state_vector_simulator.py | 32 ++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/braket/simulator_v2/simulator.py b/src/braket/simulator_v2/simulator.py index ec8921c..50eb041 100644 --- a/src/braket/simulator_v2/simulator.py +++ b/src/braket/simulator_v2/simulator.py @@ -1,5 +1,6 @@ import sys +import numpy as np from braket.default_simulator.operation_helpers import from_braket_instruction from braket.default_simulator.result_types import TargetedResultType from braket.default_simulator.simulator import BaseLocalSimulator @@ -8,7 +9,9 @@ GateModelSimulatorDeviceCapabilities, GateModelSimulatorDeviceParameters, ) +from braket.ir.jaqcd import DensityMatrix from braket.ir.jaqcd import Program as JaqcdProgram +from braket.ir.jaqcd import StateVector from braket.ir.openqasm import Program as OpenQASMProgram from braket.task_result import GateModelTaskResult @@ -88,6 +91,16 @@ def run_jaqcd( ) r = jl.simulate(self._device, [circuit_ir], qubit_count, shots) r.additionalMetadata.action = circuit_ir + if not shots: + # need to convert `list` value for `statevector` + # and `densitymatrix` result types to `np.ndarray` + for result_ind, result_type in enumerate(r.resultTypes): + if isinstance(result_type.type, StateVector) or isinstance( + result_type.type, DensityMatrix + ): + r.resultTypes[result_ind].value = np.asarray( + r.resultTypes[result_ind].value + ) return r def run_openqasm( @@ -156,6 +169,16 @@ def run_openqasm( # attach the result types if shots: r.resultTypes = results + else: + # need to convert `list` value for `statevector` + # and `densitymatrix` result types to `np.ndarray` + for result_ind, result_type in enumerate(r.resultTypes): + if isinstance(result_type.type, StateVector) or isinstance( + result_type.type, DensityMatrix + ): + r.resultTypes[result_ind].value = np.asarray( + r.resultTypes[result_ind].value + ) return r @property @@ -473,6 +496,13 @@ def run_jaqcd( ) r = jl.simulate(self._device, [circuit_ir], qubit_count, shots) r.additionalMetadata.action = circuit_ir + if not shots: + # need to convert `list` value for `densitymatrix` result type to `np.ndarray` + for result_ind, result_type in enumerate(r.resultTypes): + if isinstance(result_type.type, DensityMatrix): + r.resultTypes[result_ind].value = np.asarray( + r.resultTypes[result_ind].value + ) return r def run_openqasm( @@ -540,6 +570,13 @@ def run_openqasm( # attach the result types if shots: r.resultTypes = results + else: + # need to convert `list` value for `densitymatrix` result type to `np.ndarray` + for result_ind, result_type in enumerate(r.resultTypes): + if isinstance(result_type.type, DensityMatrix): + r.resultTypes[result_ind].value = np.asarray( + r.resultTypes[result_ind].value + ) return r @property diff --git a/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py b/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py index c2d68f4..730ab63 100644 --- a/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py +++ b/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py @@ -22,7 +22,7 @@ GateModelSimulatorDeviceCapabilities, GateModelSimulatorDeviceParameters, ) -from braket.ir.jaqcd import Expectation +from braket.ir.jaqcd import DensityMatrix, Expectation from braket.ir.jaqcd import Program as JaqcdProgram from braket.ir.openqasm import Program as OpenQASMProgram from braket.task_result import AdditionalMetadata, TaskMetadata @@ -846,3 +846,34 @@ def test_measure_targets(): assert 400 < np.sum(measurements, axis=0)[0] < 600 assert len(measurements[0]) == 1 assert result.measuredQubits == [0] + + +@pytest.mark.parametrize( + "jaqcd_string, oq3_pragma, jaqcd_type", + [ + ["densitymatrix", "density_matrix", DensityMatrix()], + ], +) +def test_simulator_analytic_value_type(jaqcd_string, oq3_pragma, jaqcd_type): + simulator = DensityMatrixSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": jaqcd_string}], + } + ) + ) + qasm = OpenQASMProgram( + source=f""" + qubit q; + h q; + #pragma braket result {oq3_pragma} + """ + ) + result = simulator.run(jaqcd, qubit_count=2, shots=0) + assert result.resultTypes[0].type == jaqcd_type + assert isinstance(result.resultTypes[0].value, np.ndarray) + result = simulator.run(qasm, shots=0) + assert result.resultTypes[0].type == jaqcd_type + assert isinstance(result.resultTypes[0].value, np.ndarray) diff --git a/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py b/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py index 0a24dbe..dc99b3c 100644 --- a/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py +++ b/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py @@ -1410,3 +1410,35 @@ def test_rotation_parameter_expressions(operation, state_vector): result = simulator.run(OpenQASMProgram(source=qasm), shots=0) assert result.resultTypes[0].type == StateVector() assert np.allclose(result.resultTypes[0].value, np.array(state_vector)) + + +@pytest.mark.parametrize( + "jaqcd_string, oq3_pragma, jaqcd_type", + [ + ["statevector", "state_vector", StateVector()], + ["densitymatrix", "density_matrix", DensityMatrix()], + ], +) +def test_simulator_analytic_value_type(jaqcd_string, oq3_pragma, jaqcd_type): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": jaqcd_string}], + } + ) + ) + qasm = OpenQASMProgram( + source=f""" + qubit q; + h q; + #pragma braket result {oq3_pragma} + """ + ) + result = simulator.run(jaqcd, qubit_count=2, shots=0) + assert result.resultTypes[0].type == jaqcd_type + assert isinstance(result.resultTypes[0].value, np.ndarray) + result = simulator.run(qasm, shots=0) + assert result.resultTypes[0].type == jaqcd_type + assert isinstance(result.resultTypes[0].value, np.ndarray) From 7f837e0d799bc9f2a9e1fd2171916e2cc628d2eb Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 6 May 2024 18:13:47 -0400 Subject: [PATCH 2/2] change: Move conversion into its own function --- src/braket/simulator_v2/simulator.py | 53 ++++++++++++---------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/braket/simulator_v2/simulator.py b/src/braket/simulator_v2/simulator.py index 50eb041..bf1c740 100644 --- a/src/braket/simulator_v2/simulator.py +++ b/src/braket/simulator_v2/simulator.py @@ -18,6 +18,23 @@ from .julia_import import jl, jlBraketSimulator +def _analytic_result_value_to_ndarray( + task_result: GateModelTaskResult, +) -> GateModelTaskResult: + """Convert any StateVector or DensityMatrix result values from raw Python lists to the expected + np.ndarray. This must be done because the wrapper Julia simulator results Python lists to comply + with the pydantic specification for ResultTypeValues. + """ + for result_ind, result_type in enumerate(task_result.resultTypes): + if isinstance(result_type.type, StateVector) or isinstance( + result_type.type, DensityMatrix + ): + task_result.resultTypes[result_ind].value = np.asarray( + task_result.resultTypes[result_ind].value + ) + return task_result + + class StateVectorSimulatorV2(BaseLocalSimulator): """A state vector simulator meant to run directly on the user's machine using a Julia backend. @@ -92,15 +109,7 @@ def run_jaqcd( r = jl.simulate(self._device, [circuit_ir], qubit_count, shots) r.additionalMetadata.action = circuit_ir if not shots: - # need to convert `list` value for `statevector` - # and `densitymatrix` result types to `np.ndarray` - for result_ind, result_type in enumerate(r.resultTypes): - if isinstance(result_type.type, StateVector) or isinstance( - result_type.type, DensityMatrix - ): - r.resultTypes[result_ind].value = np.asarray( - r.resultTypes[result_ind].value - ) + r = _analytic_result_value_to_ndarray(r) return r def run_openqasm( @@ -166,19 +175,11 @@ def run_openqasm( self._device, [circuit], qubit_count, shots, measured_qubits=measured_qubits ) r.additionalMetadata.action = openqasm_ir - # attach the result types if shots: + # attach the result types r.resultTypes = results else: - # need to convert `list` value for `statevector` - # and `densitymatrix` result types to `np.ndarray` - for result_ind, result_type in enumerate(r.resultTypes): - if isinstance(result_type.type, StateVector) or isinstance( - result_type.type, DensityMatrix - ): - r.resultTypes[result_ind].value = np.asarray( - r.resultTypes[result_ind].value - ) + r = _analytic_result_value_to_ndarray(r) return r @property @@ -497,12 +498,7 @@ def run_jaqcd( r = jl.simulate(self._device, [circuit_ir], qubit_count, shots) r.additionalMetadata.action = circuit_ir if not shots: - # need to convert `list` value for `densitymatrix` result type to `np.ndarray` - for result_ind, result_type in enumerate(r.resultTypes): - if isinstance(result_type.type, DensityMatrix): - r.resultTypes[result_ind].value = np.asarray( - r.resultTypes[result_ind].value - ) + r = _analytic_result_value_to_ndarray(r) return r def run_openqasm( @@ -571,12 +567,7 @@ def run_openqasm( if shots: r.resultTypes = results else: - # need to convert `list` value for `densitymatrix` result type to `np.ndarray` - for result_ind, result_type in enumerate(r.resultTypes): - if isinstance(result_type.type, DensityMatrix): - r.resultTypes[result_ind].value = np.asarray( - r.resultTypes[result_ind].value - ) + r = _analytic_result_value_to_ndarray(r) return r @property