From a4f4b3413f67335acfc7eb41547c90175a4d46de Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:57:52 +0200 Subject: [PATCH 01/25] feat: to_other_language Braket without QASM --- mpqp/core/circuit.py | 40 +++++---- mpqp/core/instruction/gates/custom_gate.py | 26 ++++++ mpqp/core/instruction/gates/native_gates.py | 84 +++++++++++++------ mpqp/execution/providers/aws.py | 15 +++- mpqp/qasm/myqlm_to_mpqp.py | 1 + .../instruction/gates/test_custom_gate.py | 43 +++------- .../instruction/measurement/test_basis.py | 32 +++---- tests/core/test_circuit.py | 11 +-- tests/examples/test_demonstrations.py | 61 ++++---------- tests/execution/providers/test_aws.py | 4 +- tests/execution/test_validity.py | 47 +++-------- tests/execution/test_vqa.py | 7 +- tests/noise/test_noisy_execution.py | 30 ++----- tests/qasm/test_mpqp_to_qasm.py | 8 +- tests/qasm/test_qasm_to_braket.py | 2 +- 15 files changed, 191 insertions(+), 220 deletions(-) diff --git a/mpqp/core/circuit.py b/mpqp/core/circuit.py index 77c4409c..00c167c6 100644 --- a/mpqp/core/circuit.py +++ b/mpqp/core/circuit.py @@ -1421,21 +1421,33 @@ def to_other_language( "Cannot simulate noisy circuit with CRk gate due to " "an error on AWS Braket side." ) + from braket.circuits import Circuit as BracketCircuit - qasm3_code = circuit.to_other_language( - Language.QASM3, - skip_pre_measure=skip_pre_measure, - skip_measurements=skip_measurements, - ) - - from mpqp.qasm.qasm_to_braket import qasm3_to_braket_Circuit - - braket_circuit = qasm3_to_braket_Circuit(qasm3_code) - if circuit._gphase != 0: - braket_circuit.gphase( # pyright: ignore[reportAttributeAccessIssue] - circuit._gphase - ) - circuit._gphase = 0 + braket_circuit = BracketCircuit() + for instruction in circuit.instructions: + if isinstance(instruction, (Barrier, Breakpoint)): + continue + if isinstance(instruction, Measure): + if not skip_pre_measure: + for pre_measure in instruction.pre_measure: + bracket_pre_measure = pre_measure.to_other_language( + Language.BRAKET + ) + braket_circuit.add(bracket_pre_measure, instruction.targets) + if not skip_measurements: + if isinstance(instruction, BasisMeasure): + braket_circuit.measure(instruction.targets) + continue + braket_instr = instruction.to_other_language(Language.BRAKET) + try: + target = instruction.targets + if isinstance(instruction, ControlledGate): + target = instruction.controls + target + braket_circuit.add_instruction(braket_instr, target=target) + except: + print(braket_instr) + print(type(braket_instr)) + raise if len(self.noises) == 0: return braket_circuit diff --git a/mpqp/core/instruction/gates/custom_gate.py b/mpqp/core/instruction/gates/custom_gate.py index 6ab3c113..ce732462 100644 --- a/mpqp/core/instruction/gates/custom_gate.py +++ b/mpqp/core/instruction/gates/custom_gate.py @@ -123,6 +123,32 @@ def to_other_language( dummy_circuit.rx(param, 0) return dummy_circuit.to_gate(label="CustomGate") return UnitaryGate(self.matrix) + elif language == Language.BRAKET: + from sympy import Expr + + gate_symbols = set().union( + *( + elt.free_symbols + for elt in self.matrix.flatten() + if isinstance(elt, Expr) + ) + ) + + if len(gate_symbols) > 0: + if not printing: + raise ValueError( + "Custom gates defined with symbolic variables cannot be " + "exported to Braket yet (Braket requires numeric matrices)." + ) + return None + else: + from braket.circuits import Instruction as BraketInstruction + from braket.circuits.gates import Unitary as BraketUnitary + + return BraketInstruction( + operator=BraketUnitary(self.definition.matrix), + target=list(range(self.nb_qubits)), + ) elif language == Language.QASM2: from qiskit import QuantumCircuit, qasm2 diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index cb376202..2362be2b 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -22,6 +22,7 @@ if TYPE_CHECKING: from sympy import Expr from qiskit.circuit import Parameter + from braket.circuits import FreeParameter import numpy as np import numpy.typing as npt @@ -82,6 +83,25 @@ def _qiskit_parameter_adder( return qiskit_param +@conditional_typechecked +def _sympy_to_braket_param(val: Expr | float) -> "float | FreeParameter": + from sympy import Expr, Symbol + from braket.circuits import FreeParameter + + if isinstance(val, Symbol): + return FreeParameter(str(val)) + elif isinstance(val, Expr): + if val.free_symbols: + return FreeParameter(str(val)) # note: Braket won't parse expressions + else: + try: + return float(val.evalf()) # pyright: ignore[reportArgumentType] + except Exception as e: + raise ValueError(f"Failed to evaluate sympy expression '{val}': {e}") + else: + return float(val) + + @conditional_typechecked class NativeGate(Gate, SimpleClassReprABC): """The standard on which we rely, OpenQASM, comes with a set of gates @@ -224,24 +244,25 @@ def to_other_language( language: Language = Language.QISKIT, qiskit_parameters: Optional[set["Parameter"]] = None, ): - if qiskit_parameters is None: - qiskit_parameters = set() + try: theta = float(self.theta) except: theta = self.theta if language == Language.QISKIT: + if qiskit_parameters is None: + qiskit_parameters = set() return self.qiskit_gate(_qiskit_parameter_adder(theta, qiskit_parameters)) elif language == Language.BRAKET: - from sympy import Expr + from braket.circuits import Instruction - # TODO: handle symbolic parameters for Braket - if isinstance(theta, Expr): - raise NotImplementedError( - "Symbolic expressions are not yet supported for braket " - "export, this feature is coming very soon!" - ) - return self.braket_gate(theta) + connection = self.targets + if isinstance(self, ControlledGate): + connection += self.controls + return Instruction( + operator=self.braket_gate(_sympy_to_braket_param(theta)), + target=list(range(len(connection))), + ) elif language == Language.CIRQ: return self.cirq_gate(theta) if language == Language.QASM2: @@ -347,7 +368,14 @@ def to_other_language( if language == Language.QISKIT: return self.qiskit_gate() elif language == Language.BRAKET: - return self.braket_gate() + from braket.circuits import Instruction + + connection = self.targets + if isinstance(self, ControlledGate): + connection += self.controls + return Instruction( + operator=self.braket_gate(), target=list(range(len(connection))) + ) elif language == Language.CIRQ: return self.cirq_gate elif language == Language.QASM2: @@ -432,7 +460,14 @@ def to_other_language( return self.qiskit_gate(label=self.label) return self.qiskit_gate() elif language == Language.BRAKET: - return self.braket_gate() + from braket.circuits import Instruction + + connection = self.targets + if isinstance(self, ControlledGate): + connection += self.controls + return Instruction( + operator=self.braket_gate(), target=list(range(len(connection))) + ) elif language == Language.CIRQ: return self.cirq_gate elif language == Language.QASM2: @@ -1080,20 +1115,19 @@ def to_other_language( lam=_qiskit_parameter_adder(self.gamma, qiskit_parameters), ) elif language == Language.BRAKET: - from sympy import Expr - - # TODO handle symbolic parameters - if ( - isinstance(self.theta, Expr) - or isinstance(self.phi, Expr) - or isinstance(self.gamma, Expr) - ): - raise NotImplementedError( - "Symbolic expressions are not yet supported for braket " - "export, this feature is coming very soon!" - ) + from braket.circuits import Instruction - return self.braket_gate(self.theta, self.phi, self.gamma) + connection = self.targets + if isinstance(self, ControlledGate): + connection += self.controls + return Instruction( + operator=self.braket_gate( + _sympy_to_braket_param(self.theta), + _sympy_to_braket_param(self.phi), + _sympy_to_braket_param(self.gamma), + ), + target=list(range(len(connection))), + ) elif language == Language.CIRQ: return self.cirq_gate(self.theta, self.phi, self.gamma) elif language == Language.QASM2: diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index e834717f..833717d0 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -299,8 +299,21 @@ def submit_job_braket(job: Job) -> tuple[str, "QuantumTask"]: if TYPE_CHECKING: assert isinstance(braket_circuit, Circuit) - if job.job_type == JobType.STATE_VECTOR: + # rebind safe_retrieve_samples from braket to Normalize the probability + # because the bracket does not do so and this causes a crash. + from braket.default_simulator.state_vector_simulation import ( + StateVectorSimulation, + ) + + def safe_retrieve_samples(self): # pyright: ignore[reportMissingParameterType] + probs = self.probabilities + probs = probs / np.sum(probs) + return np.random.choice(len(self._state_vector), p=probs, size=self._shots) + + StateVectorSimulation.retrieve_samples = safe_retrieve_samples + # ---- + braket_circuit.state_vector() # pyright: ignore[reportAttributeAccessIssue] job.status = JobStatus.RUNNING task = device.run(braket_circuit, shots=0, inputs=None) diff --git a/mpqp/qasm/myqlm_to_mpqp.py b/mpqp/qasm/myqlm_to_mpqp.py index 941e4807..f81f0848 100644 --- a/mpqp/qasm/myqlm_to_mpqp.py +++ b/mpqp/qasm/myqlm_to_mpqp.py @@ -94,6 +94,7 @@ def from_myqlm_to_mpqp(circuit: my_QLM_Circuit) -> QCircuit: 'CSIGN': CZ, 'SWAP': SWAP, 'U': U, + 'U3': U, 'U1': P, 'ISWAP': None, 'SQRTSWAP': None, diff --git a/tests/core/instruction/gates/test_custom_gate.py b/tests/core/instruction/gates/test_custom_gate.py index 713ba9e2..cddf84c5 100644 --- a/tests/core/instruction/gates/test_custom_gate.py +++ b/tests/core/instruction/gates/test_custom_gate.py @@ -19,7 +19,6 @@ from mpqp.execution.runner import run from mpqp.gates import * from mpqp.tools.circuit import random_circuit -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning from mpqp.tools.maths import is_unitary, matrix_eq, rand_orthogonal_matrix @@ -53,12 +52,7 @@ def test_random_orthogonal_matrix(circ_size: int, device: AvailableDevice): for _ in range(targets_start + gate_size, circ_size): exp_state_vector = np.kron(exp_state_vector, np.array([1, 0])) - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - result = run(c, device) + result = run(c, device) # we reduce the precision because of approximation errors coming from CustomGate usage assert isinstance(result, Result) @@ -83,19 +77,9 @@ def test_custom_gate_with_native_gates(device: AvailableDevice): ) c2 = QCircuit([X(0), H(1), CNOT(1, 2), Z(0)]) - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - result1 = run(c1, device) + result1 = run(c1, device) - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - result2 = run(c2, device) + result2 = run(c2, device) # we reduce the precision because of approximation errors coming from CustomGate usage assert isinstance(result1, Result) @@ -109,13 +93,8 @@ def test_custom_gate_with_random_circuit(circ_size: int, device: AvailableDevice matrix = random_circ.to_matrix() custom_gate_circ = QCircuit([CustomGate(matrix, list(range(circ_size)))]) - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - result1 = run(random_circ, device) - result2 = run(custom_gate_circ, device) + result1 = run(random_circ, device) + result2 = run(custom_gate_circ, device) assert isinstance(result1, Result) assert isinstance(result2, Result) @@ -186,13 +165,11 @@ def _test_execution_equivalence( ) targets = [position for _, position in gates_n_positions] - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - result_custom_gate = run(QCircuit([CustomGate(matrix, targets)]), device) - result_circuit = run(circuit, device) + print(matrix) + result_custom_gate = run(QCircuit([CustomGate(matrix, targets)]), device) + result_circuit = run(circuit, device) + print(result_circuit) + print(result_custom_gate) assert matrix_eq( result_custom_gate.amplitudes, result_circuit.amplitudes, 1e-4, 1e-4 ) diff --git a/tests/core/instruction/measurement/test_basis.py b/tests/core/instruction/measurement/test_basis.py index 76f630ad..5aed5b4d 100644 --- a/tests/core/instruction/measurement/test_basis.py +++ b/tests/core/instruction/measurement/test_basis.py @@ -17,7 +17,6 @@ HadamardBasis, VariableSizeBasis, ) -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning from mpqp.tools.maths import matrix_eq @@ -308,14 +307,9 @@ def test_valid_run_custom_basis_state_vector_one_qubit( ): vectors = [np.array([np.sqrt(3) / 2, 1 / 2]), np.array([-1 / 2, np.sqrt(3) / 2])] - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - result = run( - circuit + QCircuit([BasisMeasure(basis=Basis(vectors), shots=0)]), device - ) + result = run( + circuit + QCircuit([BasisMeasure(basis=Basis(vectors), shots=0)]), device + ) assert isinstance(result, Result) assert matrix_eq(vectors[expected_vector_index], result.amplitudes) @@ -323,14 +317,14 @@ def test_valid_run_custom_basis_state_vector_one_qubit( def test_run_custom_basis_sampling_one_qubit(): vectors = [np.array([np.sqrt(3) / 2, 1 / 2]), np.array([-1 / 2, np.sqrt(3) / 2])] basis = Basis(vectors) - with pytest.warns(UnsupportedBraketFeaturesWarning): - run( - QCircuit([X(0), X(0), BasisMeasure(basis=basis)]), - [ - IBMDevice.AER_SIMULATOR, - ATOSDevice.MYQLM_PYLINALG, - AWSDevice.BRAKET_LOCAL_SIMULATOR, - GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, - ], - ) + + run( + QCircuit([X(0), X(0), BasisMeasure(basis=basis)]), + [ + IBMDevice.AER_SIMULATOR, + ATOSDevice.MYQLM_PYLINALG, + AWSDevice.BRAKET_LOCAL_SIMULATOR, + GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, + ], + ) assert True diff --git a/tests/core/test_circuit.py b/tests/core/test_circuit.py index 5258b6cc..34be2c6b 100644 --- a/tests/core/test_circuit.py +++ b/tests/core/test_circuit.py @@ -59,7 +59,7 @@ statevector_from_random_circuit, ) from mpqp.tools.display import one_lined_repr -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning, NonReversibleWarning +from mpqp.tools.errors import NonReversibleWarning from mpqp.tools.generics import Matrix, OneOrMany from mpqp.tools.maths import matrix_eq import random @@ -560,14 +560,7 @@ def test_without_measurements(circuit: QCircuit, printed_result_filename: str): def test_to_other_language( circuit: QCircuit, args: tuple[Language], result_type: type, result_repr: str ): - language = Language.QISKIT if len(args) == 0 else args[0] - # TODO: test other languages - if language == Language.BRAKET: - with pytest.warns(UnsupportedBraketFeaturesWarning) as record: - converted_circuit = circuit.to_other_language(*args) - assert len(record) == 1 - else: - converted_circuit = circuit.to_other_language(*args) + converted_circuit = circuit.to_other_language(*args) assert type(converted_circuit) == result_type if isinstance(converted_circuit, QiskitCircuit): assert repr(converted_circuit.data) == result_repr diff --git a/tests/examples/test_demonstrations.py b/tests/examples/test_demonstrations.py index 31384a01..6522c129 100644 --- a/tests/examples/test_demonstrations.py +++ b/tests/examples/test_demonstrations.py @@ -17,14 +17,6 @@ # TODO: add CIRQ local simulator devices to this file -def warn_guard(device: AvailableDevice, run: Callable[[], Any]): - if isinstance(device, AWSDevice): - with pytest.warns(UnsupportedBraketFeaturesWarning): - return run() - else: - return run() - - def test_sample_demo(): # Declaration of the circuit with the right size circuit = QCircuit(4) @@ -50,7 +42,7 @@ def test_sample_demo(): circuit.add(BasisMeasure([0, 1, 2, 3], shots=2000)) # Run the circuit on a selected device - runner = lambda: run( + run( circuit, [ IBMDevice.AER_SIMULATOR, @@ -65,8 +57,6 @@ def test_sample_demo(): ], ) - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) - assert True @@ -123,7 +113,7 @@ def test_statevector_demo(): ) # when no measure in the circuit, must run in statevector mode - runner = lambda: run( + run( circuit, [ IBMDevice.AER_SIMULATOR_STATEVECTOR, @@ -135,13 +125,11 @@ def test_statevector_demo(): ], ) - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) - # same when we add a BasisMeasure with 0 shots circuit.add(BasisMeasure([0, 1, 2, 3], shots=0)) # Run the circuit on a selected device - runner = lambda: run( + run( circuit, [ IBMDevice.AER_SIMULATOR_STATEVECTOR, @@ -153,8 +141,6 @@ def test_statevector_demo(): ], ) - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) - assert True @@ -202,7 +188,7 @@ def test_observable_demo(shots: int): circuit.add(ExpectationMeasure(obs, shots=shots)) # Running the computation on myQLM and on Aer simulator, then retrieving the results - runner = lambda: run( + run( circuit, [ ATOSDevice.MYQLM_PYLINALG, @@ -217,8 +203,6 @@ def test_observable_demo(shots: int): ], ) - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) - assert True @@ -234,8 +218,8 @@ def test_aws_qasm_executions(): c[0] = measure q[0]; c[1] = measure q[1];""" - runner = lambda: qasm3_to_braket_Circuit(qasm_str) - circuit = warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) + with pytest.warns(UnsupportedBraketFeaturesWarning): + circuit = qasm3_to_braket_Circuit(qasm_str) device.run(circuit, shots=100).result() @@ -262,9 +246,7 @@ def test_aws_mpqp_executions(): # Add measurement circuit.add(BasisMeasure([0, 1, 2, 3], shots=2000)) - runner = lambda: run(circuit, AWSDevice.BRAKET_LOCAL_SIMULATOR) - - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) + run(circuit, AWSDevice.BRAKET_LOCAL_SIMULATOR) ##################################################### @@ -288,10 +270,7 @@ def test_aws_mpqp_executions(): circuit.add(ExpectationMeasure(obs, shots=0)) # Running the computation on myQLM and on Braket simulator, then retrieving the results - runner = lambda: run( - circuit, [AWSDevice.BRAKET_LOCAL_SIMULATOR, ATOSDevice.MYQLM_PYLINALG] - ) - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) + run(circuit, [AWSDevice.BRAKET_LOCAL_SIMULATOR, ATOSDevice.MYQLM_PYLINALG]) ##################################################### @@ -301,10 +280,7 @@ def test_aws_mpqp_executions(): ) # Running the computation on myQLM and on Aer simulator, then retrieving the results - runner = lambda: run( - circuit, [AWSDevice.BRAKET_LOCAL_SIMULATOR, ATOSDevice.MYQLM_PYLINALG] - ) - warn_guard(AWSDevice.BRAKET_LOCAL_SIMULATOR, runner) + run(circuit, [AWSDevice.BRAKET_LOCAL_SIMULATOR, ATOSDevice.MYQLM_PYLINALG]) def test_all_native_gates(): @@ -320,13 +296,12 @@ def test_all_native_gates(): circuit.to_other_language(Language.QASM2) circuit.to_other_language(Language.QASM3) - with pytest.warns(UnsupportedBraketFeaturesWarning): - run( - circuit, - [ - ATOSDevice.MYQLM_PYLINALG, - ATOSDevice.MYQLM_CLINALG, - IBMDevice.AER_SIMULATOR_STATEVECTOR, - AWSDevice.BRAKET_LOCAL_SIMULATOR, - ], - ) + run( + circuit, + [ + ATOSDevice.MYQLM_PYLINALG, + ATOSDevice.MYQLM_CLINALG, + IBMDevice.AER_SIMULATOR_STATEVECTOR, + AWSDevice.BRAKET_LOCAL_SIMULATOR, + ], + ) diff --git a/tests/execution/providers/test_aws.py b/tests/execution/providers/test_aws.py index 254f87e0..f195f6f9 100644 --- a/tests/execution/providers/test_aws.py +++ b/tests/execution/providers/test_aws.py @@ -4,7 +4,6 @@ from mpqp.core import QCircuit from mpqp.execution import AWSDevice, run from mpqp.gates import * -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning @pytest.mark.parametrize( @@ -17,5 +16,4 @@ ], ) def test_braket_non_contiguous_qubits(circuit: QCircuit): - with pytest.warns(UnsupportedBraketFeaturesWarning): - run(circuit, AWSDevice.BRAKET_LOCAL_SIMULATOR) + run(circuit, AWSDevice.BRAKET_LOCAL_SIMULATOR) diff --git a/tests/execution/test_validity.py b/tests/execution/test_validity.py index d6fb3252..d80235f6 100644 --- a/tests/execution/test_validity.py +++ b/tests/execution/test_validity.py @@ -41,7 +41,6 @@ from mpqp.tools.circuit import random_gate, random_noise from mpqp.tools.errors import ( DeviceJobIncompatibleError, - UnsupportedBraketFeaturesWarning, ) from mpqp.tools.maths import matrix_eq @@ -126,8 +125,7 @@ def hae_3_qubit_circuit( def test_state_vector_result_HEA_ansatz( parameters: list[float], expected_vector: npt.NDArray[np.complex128] ): - with pytest.warns(UnsupportedBraketFeaturesWarning): - batch = run(hae_3_qubit_circuit(*parameters), state_vector_devices) + batch = run(hae_3_qubit_circuit(*parameters), state_vector_devices) assert isinstance(batch, BatchResult) for result in batch: assert isinstance(result, Result) @@ -197,8 +195,7 @@ def test_state_vector_result_HEA_ansatz( ], ) def test_state_vector_various_native_gates(gates: list[Gate], expected_vector: Matrix): - with pytest.warns(UnsupportedBraketFeaturesWarning): - batch = run(QCircuit(gates), state_vector_devices) + batch = run(QCircuit(gates), state_vector_devices) assert isinstance(batch, BatchResult) for result in batch: assert isinstance(result, Result) @@ -247,8 +244,7 @@ def test_state_vector_various_native_gates(gates: list[Gate], expected_vector: M def test_sample_basis_state_in_samples(gates: list[Gate], basis_states: list[str]): c = QCircuit(gates) c.add(BasisMeasure(list(range(c.nb_qubits)), shots=10000)) - with pytest.warns(UnsupportedBraketFeaturesWarning): - batch = run(c, sampling_devices) + batch = run(c, sampling_devices) assert isinstance(batch, BatchResult) nb_states = len(basis_states) for result in batch: @@ -274,8 +270,7 @@ def test_sample_counts_in_trust_interval(instructions: list[Gate]): assert isinstance(res, Result) expected_counts = [int(count) for count in np.round(shots * res.probabilities)] c.add(BasisMeasure(list(range(c.nb_qubits)), shots=shots)) - with pytest.warns(UnsupportedBraketFeaturesWarning): - batch = run(c, sampling_devices) + batch = run(c, sampling_devices) assert isinstance(batch, BatchResult) for result in batch: assert isinstance(result, Result) @@ -328,8 +323,7 @@ def test_observable_ideal_case( expected_value = ( expected_vector.transpose().conjugate().dot(observable.dot(expected_vector)) ) - with pytest.warns(UnsupportedBraketFeaturesWarning): - batch = run(c, sampling_devices) + batch = run(c, sampling_devices) assert isinstance(batch, BatchResult) for result in batch: print(result.device) @@ -375,23 +369,12 @@ def test_validity_run_job_type(device: AvailableDevice, circuits_type: list[QCir if not device.is_remote(): if device.supports_samples(): - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - assert run(circuit_samples, device) is not None + assert run(circuit_samples, device) is not None else: - with pytest.raises(NotImplementedError): - run(circuit_samples, device) + run(circuit_samples, device) if device.supports_state_vector(): - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - assert run(circuit_state_vector, device) is not None + assert run(circuit_state_vector, device) is not None else: if isinstance(device, IBMDevice) and not device.supports_state_vector(): with pytest.raises(DeviceJobIncompatibleError): @@ -407,12 +390,7 @@ def test_validity_run_job_type(device: AvailableDevice, circuits_type: list[QCir circuit_observable.measurements[0].shots = 10 assert run(circuit_observable, device) is not None else: - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - assert run(circuit_observable, device) is not None + assert run(circuit_observable, device) is not None else: if isinstance(device, IBMDevice): @@ -429,12 +407,7 @@ def test_validity_run_job_type(device: AvailableDevice, circuits_type: list[QCir circuit_observable_ideal.measurements[0].shots = 10 assert run(circuit_observable_ideal, device) is not None else: - with ( - pytest.warns(UnsupportedBraketFeaturesWarning) - if isinstance(device, AWSDevice) - else contextlib.suppress() - ): - assert run(circuit_observable_ideal, device) is not None + assert run(circuit_observable_ideal, device) is not None else: if isinstance(device, IBMDevice): with pytest.raises(DeviceJobIncompatibleError): diff --git a/tests/execution/test_vqa.py b/tests/execution/test_vqa.py index 30a89628..4fbfefa4 100644 --- a/tests/execution/test_vqa.py +++ b/tests/execution/test_vqa.py @@ -20,7 +20,6 @@ from mpqp.execution.vqa import Optimizer, minimize from mpqp.execution.vqa.vqa import OptimizableFunc from mpqp.gates import * -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning # the symbols function is a bit wacky, so some manual type definition is needed here theta: Expr = symbols("θ") @@ -56,11 +55,7 @@ def run(): assert minimize(circ, Optimizer.BFGS, device)[0] - minimum < 0.05 try: - if isinstance(device, AWSDevice): - with pytest.warns(UnsupportedBraketFeaturesWarning): - run() - else: - run() + run() except (ValueError, NotImplementedError) as err: if "not handled" not in str(err): raise diff --git a/tests/noise/test_noisy_execution.py b/tests/noise/test_noisy_execution.py index e0afac27..b99fbb49 100644 --- a/tests/noise/test_noisy_execution.py +++ b/tests/noise/test_noisy_execution.py @@ -3,7 +3,7 @@ import sys from itertools import product -from typing import Any, Callable, Iterable +from typing import Any import numpy as np import pytest @@ -24,7 +24,6 @@ ) from mpqp.gates import * from mpqp.noise import AmplitudeDamping, BitFlip, Depolarizing, PhaseDamping -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning from mpqp.tools.theoretical_simulation import validate_noisy_circuit noisy_devices: list[Any] = [ @@ -37,20 +36,6 @@ noisy_devices = [AWSDevice.BRAKET_LOCAL_SIMULATOR, IBMDevice.AER_SIMULATOR] -def filter_braket_warning( - action: Callable[[AvailableDevice], Any], - devices: AvailableDevice, -): - if ( - isinstance(devices, Iterable) - and any(isinstance(device, AWSDevice) for device in devices) - ) or isinstance(devices, AWSDevice): - with pytest.warns((UnsupportedBraketFeaturesWarning)): - return action(devices) - else: - return action(devices) - - @pytest.fixture def circuit(): return QCircuit( @@ -105,8 +90,7 @@ def test_noisy_expectation_value_execution_without_error( PhaseDamping(0.6), ] ) - with pytest.warns(UnsupportedBraketFeaturesWarning): - run(circuit, devices) + run(circuit, devices) assert True @@ -126,8 +110,7 @@ def test_all_native_gates_global_noise_execution_without_error( PhaseDamping(0.4, gates=[CNOT, H]), ] ) - with pytest.warns(UnsupportedBraketFeaturesWarning): - run(circuit, devices) + run(circuit, devices) assert True @@ -148,8 +131,7 @@ def test_all_native_gates_local_noise( PhaseDamping(0.4, [0, 1, 2], gates=[CNOT, H]), ] ) - with pytest.warns(UnsupportedBraketFeaturesWarning): - run(circuit, devices) + run(circuit, devices) assert True @@ -165,6 +147,4 @@ def test_validate_depolarizing_noise( circuit: QCircuit, depol_noise: float, shots: int, device: AvailableDevice ): circuit.add(Depolarizing(depol_noise)) - assert filter_braket_warning( - lambda d: validate_noisy_circuit(circuit, shots, d), device - ) + assert validate_noisy_circuit(circuit, shots, device) diff --git a/tests/qasm/test_mpqp_to_qasm.py b/tests/qasm/test_mpqp_to_qasm.py index add62e31..cd481b59 100644 --- a/tests/qasm/test_mpqp_to_qasm.py +++ b/tests/qasm/test_mpqp_to_qasm.py @@ -85,7 +85,7 @@ y q[0]; z q[0]; p(pi) q[0]; -u(pi,pi/2,2.5) q[0]; +u3(pi,pi/2,2.5) q[0]; t q[0]; cx q[0],q[1]; cp(pi) q[1],q[0]; @@ -197,7 +197,7 @@ y q[0]; z q[0]; p(pi) q[0]; -u(pi,pi/2,2.5) q[0]; +u3(pi,pi/2,2.5) q[0]; t q[0]; cx q[0],q[1]; cp(pi) q[1],q[0]; @@ -355,7 +355,7 @@ def test_mpqp_to_qasm_custom_gate(instructions: list[Instruction]): y q[0]; z q[0]; p(pi) q[0]; -u(pi,pi/2,2.5) q[0]; +u3(pi,pi/2,2.5) q[0]; t q[0]; cx q[0],q[1]; cp(pi) q[1],q[0]; @@ -456,7 +456,7 @@ def test_mpqp_to_qasm_custom_gate(instructions: list[Instruction]): y q[0],q[1]; p(pi) q; p(pi) q[0]; -u(pi,pi/2,2.5) q[0]; +u3(pi,pi/2,2.5) q[0]; t q[0]; cx q[0],q[1]; cp(pi) q[1],q[0]; diff --git a/tests/qasm/test_qasm_to_braket.py b/tests/qasm/test_qasm_to_braket.py index 220efe76..d738f77f 100644 --- a/tests/qasm/test_qasm_to_braket.py +++ b/tests/qasm/test_qasm_to_braket.py @@ -3,7 +3,7 @@ from braket.circuits.gates import CNot, H from mpqp.qasm.qasm_to_braket import qasm3_to_braket_Circuit -from mpqp.tools.errors import UnsupportedBraketFeaturesWarning +from mpqp.tools import UnsupportedBraketFeaturesWarning @pytest.mark.parametrize( From 50d8018cb7dfcfec5ec1969f998523c7aea38bb5 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:11:35 +0200 Subject: [PATCH 02/25] feat: add pre_transpile attribute and update grouping methods for ExpectationMeasure --- .../measurement/expectation_value.py | 41 +++++++++++++++---- .../instruction/measurement/pauli_string.py | 12 +++--- mpqp/execution/providers/aws.py | 25 +++++++---- mpqp/tools/pauli_grouping.py | 1 + 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index d23f79e8..cea0fce8 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -83,8 +83,8 @@ def __init__( self._is_diagonal = None self._diag_elements: Optional[npt.NDArray[np.float64]] = None self.label = label - self.transpile = None "See parameter description." + self.pre_transpile = None if isinstance(observable, PauliString): self.nb_qubits = observable.nb_qubits @@ -312,12 +312,15 @@ def to_other_language( return QLMObservable(self.nb_qubits, matrix=self.matrix) elif language == Language.BRAKET: - from braket.circuits.observables import Hermitian + if self._pauli_string: + return self.pauli_string.to_other_language(Language.BRAKET) + else: + from braket.circuits.observables import Hermitian - return Hermitian( - self.matrix, - display_name=self.label if self.label is not None else "Hermitian", - ) + return Hermitian( + self.matrix, + display_name=self.label if self.label is not None else "Hermitian", + ) elif language == Language.CIRQ: return self.pauli_string.to_other_language(Language.CIRQ, circuit) else: @@ -388,7 +391,7 @@ def __init__( targets: Optional[list[int]] = None, shots: int = 0, commuting_type: CommutingTypes = CommutingTypes.QUBITWISE, - grouping_method: GroupingMethods = GroupingMethods.GREEDY, + grouping_method: GroupingMethods = GroupingMethods.QISKIT, label: Optional[str] = None, optimize_measurement: Optional[bool] = True, optim_diagonal: Optional[bool] = False, @@ -405,6 +408,7 @@ def __init__( """See parameter description.""" self.optimize_measurement = optimize_measurement """See parameter description.""" + self.pre_transpile = None if isinstance(observable, Observable): observable = [observable] else: @@ -444,7 +448,7 @@ def __init__( obs._diag_elements, # pyright: ignore[reportPrivateUsage] f"{default_label}_{label_counter}", ) - obs_new.transpile = obs.transpile + obs_new.pre_transpile = obs.pre_transpile label_counter += 1 self.observables.append(obs_new) @@ -525,6 +529,27 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: from mpqp.tools.pauli_grouping import pauli_grouping_greedy return pauli_grouping_greedy(unique_monos, self.commuting_type) + elif self.grouping_method == GroupingMethods.QISKIT: + from qiskit.quantum_info import PauliList + from mpqp.core.instruction.measurement.pauli_string import ( + pauli_string_from_str, + ) + + pauli_labels = [mono.name.replace("@", "") for mono in unique_monos] + pauli_list = PauliList(pauli_labels) + + # Choose grouping based on commutativity type + if self.commuting_type == CommutingTypes.QUBITWISE: + grouped = pauli_list.group_qubit_wise_commuting() + else: + grouped = pauli_list.group_commuting() + + grouped_monomials = [ + [pauli_string_from_str(mono.to_label()) for mono in pauli] + for pauli in grouped + ] + + return grouped_monomials # pyright: ignore[reportReturnType] else: raise NotImplementedError(f"{self.grouping_method} is not yet supported.") diff --git a/mpqp/core/instruction/measurement/pauli_string.py b/mpqp/core/instruction/measurement/pauli_string.py index ad2f1d27..b0f41ae2 100644 --- a/mpqp/core/instruction/measurement/pauli_string.py +++ b/mpqp/core/instruction/measurement/pauli_string.py @@ -59,6 +59,7 @@ class GroupingMethods(Enum): COLORING_DB = auto() COLORING_DSATUR = auto() CLIQUE_REMOVING = auto() + QISKIT = auto() @conditional_typechecked @@ -1042,10 +1043,7 @@ def commutes_with( ) elif method == CommutingTypes.FULL: return ( - sum( - 1 for a, b in zip(self.atoms, other.atoms) if not a.commutes_with(b) - ) - % 2 + sum(not a.commutes_with(b) for a, b in zip(self.atoms, other.atoms)) % 2 == 0 ) else: @@ -1310,7 +1308,7 @@ def commutes_with( f"Expected a PauliStringAtom in parameter but got {type(other).__name__}" ) if method == CommutingTypes.FULL: - return other == I or self == I or self == other + return other.label == "I" or self.label == "I" or self.label == other.label raise ValueError( f"PauliStringAtoms can only fully commutes with each others, instead received {method}" ) @@ -1406,7 +1404,7 @@ def to_other_language( def pauli_string_from_str( compact_str: str, dict_value: Optional[dict[str, Coef]] = None -) -> PauliString: +) -> PauliString | PauliStringMonomial: """ Construct a `PauliString` from a string representation. @@ -1452,6 +1450,8 @@ def pauli_string_from_str( atoms = [globals()[ch] for ch in atoms_str] monomials.append(PauliStringMonomial(coef, atoms)) + if len(monomials) == 1: + return monomials[0] return PauliString(monomials) diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 833717d0..a22d496e 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -161,22 +161,29 @@ def run_braket_observable(job: Job): results = {} errors = {} if job.measure.optimize_measurement: - grouping = job.measure.get_pauli_grouping() from mpqp.tools.pauli_grouping import ( find_qubitwise_rotations, pauli_monomial_eigenvalues, ) + if job.measure.pre_transpile is None: + grouping = job.measure.get_pauli_grouping() + transpiled_groupings = [ + QCircuit(find_qubitwise_rotations(group)).to_other_language( + Language.BRAKET + ) + for group in grouping + ] + else: + grouping, transpiled_groupings = job.measure.pre_transpile + expectation_values = {} - for group in grouping: - transpiled_pre_measure = QCircuit( - find_qubitwise_rotations(group) - ).to_other_language(Language.BRAKET) + for group, transpile_group in zip(grouping, transpiled_groupings): job.status = JobStatus.RUNNING if job.measure.shots == 0: from copy import deepcopy - cirq = deepcopy(transpiled_circuit + transpiled_pre_measure) + cirq = deepcopy(transpiled_circuit + transpile_group) cirq.state_vector() # pyright: ignore[reportAttributeAccessIssue] local_result = device.run(cirq, shots=0, inputs=None).result() @@ -187,7 +194,7 @@ def run_braket_observable(job: Job): sorted_values.append(float(np.abs(values[i]) ** 2)) else: local_result = device.run( - transpiled_circuit + transpiled_pre_measure, + transpiled_circuit + transpile_group, shots=job.measure.shots, inputs=None, ) @@ -328,10 +335,10 @@ def safe_retrieve_samples(self): # pyright: ignore[reportMissingParameterType] # TODO : [multi-obs] update this to take into account the case when we have list of Observables if TYPE_CHECKING: assert isinstance(job.measure, ExpectationMeasure) - if job.measure.observables[0].transpile is None: + if job.measure.observables[0].pre_transpile is None: herm_op = job.measure.observables[0].to_other_language(Language.BRAKET) else: - herm_op = job.measure.observables[0].transpile + herm_op = job.measure.observables[0].pre_transpile braket_circuit.expectation( # pyright: ignore[reportAttributeAccessIssue] observable=herm_op, target=job.measure.targets ) diff --git a/mpqp/tools/pauli_grouping.py b/mpqp/tools/pauli_grouping.py index 8b08a80b..80105dc8 100644 --- a/mpqp/tools/pauli_grouping.py +++ b/mpqp/tools/pauli_grouping.py @@ -51,6 +51,7 @@ def pauli_grouping_greedy(monomials: list[PauliStringMonomial], type: CommutingT [[I@X@X, Y@Y@Z, I@I@I], [-3*Z@Y@X, -1*Z@Z@Y], [Y@X@Y], [2*X@X@Y]] """ groups: list[list[PauliStringMonomial]] = [] + for monomial in monomials: added = False for group in groups: From 08500650f70650516354dba33dbe9b78749bbd54 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:31:05 +0200 Subject: [PATCH 03/25] feat: update observable pre_transpile attribute --- mpqp/execution/providers/atos.py | 4 ++-- mpqp/execution/providers/aws.py | 24 +++++++++++++++--------- mpqp/execution/providers/google.py | 4 ++-- mpqp/execution/providers/ibm.py | 8 ++++---- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/mpqp/execution/providers/atos.py b/mpqp/execution/providers/atos.py index c4c7147e..1a5ba09f 100644 --- a/mpqp/execution/providers/atos.py +++ b/mpqp/execution/providers/atos.py @@ -256,10 +256,10 @@ def generate_observable_job(myqlm_circuit: "Circuit", job: Job) -> list["JobQLM" assert job.measure is not None and isinstance(job.measure, ExpectationMeasure) result = [] for obs in job.measure.observables: - if obs.transpile is None: + if obs.pre_transpile is None: qlm_obs = obs.to_other_language(Language.MY_QLM) else: - qlm_obs = obs.transpile + qlm_obs = obs.pre_transpile result.append( myqlm_circuit.to_job( job_type="OBS", diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index a22d496e..59c30d65 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -168,22 +168,27 @@ def run_braket_observable(job: Job): if job.measure.pre_transpile is None: grouping = job.measure.get_pauli_grouping() - transpiled_groupings = [ + transpiled_pre_measures = [ QCircuit(find_qubitwise_rotations(group)).to_other_language( Language.BRAKET ) for group in grouping ] + eigenvalues = [ + {monom.name: pauli_monomial_eigenvalues(monom) for monom in group} + for group in grouping + ] + else: - grouping, transpiled_groupings = job.measure.pre_transpile + eigenvalues, transpiled_pre_measures = job.measure.pre_transpile expectation_values = {} - for group, transpile_group in zip(grouping, transpiled_groupings): + for eigenvalues, pre_measure in zip(eigenvalues, transpiled_pre_measures): job.status = JobStatus.RUNNING if job.measure.shots == 0: from copy import deepcopy - cirq = deepcopy(transpiled_circuit + transpile_group) + cirq = deepcopy(transpiled_circuit + pre_measure) cirq.state_vector() # pyright: ignore[reportAttributeAccessIssue] local_result = device.run(cirq, shots=0, inputs=None).result() @@ -194,7 +199,7 @@ def run_braket_observable(job: Job): sorted_values.append(float(np.abs(values[i]) ** 2)) else: local_result = device.run( - transpiled_circuit + transpile_group, + transpiled_circuit + pre_measure, shots=job.measure.shots, inputs=None, ) @@ -210,17 +215,18 @@ def run_braket_observable(job: Job): ) else: sorted_values.append(0) - for monom in group: + for name, eigenvalue in eigenvalues.items(): expectation_value: float = np.dot( - pauli_monomial_eigenvalues(monom), + eigenvalue, np.array(sorted_values, dtype=np.float64), ) - expectation_values.update({monom.name: expectation_value}) + expectation_values[name] = expectation_value for i, obs in enumerate(job.measure.observables): string = obs.pauli_string local: float = 0 for monoms in string.monomials: - assert isinstance(monoms.coef, (int, float)) + if TYPE_CHECKING: + assert isinstance(monoms.coef, (int, float)) local += expectation_values[monoms.name] * monoms.coef results.update({f"observable_{i}": local}) errors.update({f"observable_{len(errors)}": None}) diff --git a/mpqp/execution/providers/google.py b/mpqp/execution/providers/google.py index 82c76d13..1c3878a8 100644 --- a/mpqp/execution/providers/google.py +++ b/mpqp/execution/providers/google.py @@ -255,12 +255,12 @@ def run_cirq_observable( expectation_values = {} for obs in job.measure.observables: - if obs.transpile is None: + if obs.pre_transpile is None: cirq_obs = obs.to_other_language( language=Language.CIRQ, circuit=circuit ) else: - cirq_obs = obs.transpile + cirq_obs = obs.pre_transpile if TYPE_CHECKING: assert type(cirq_obs) in (CirqPauliSum, CirqPauliString) job.status = JobStatus.RUNNING diff --git a/mpqp/execution/providers/ibm.py b/mpqp/execution/providers/ibm.py index 879d3f8f..443751bb 100644 --- a/mpqp/execution/providers/ibm.py +++ b/mpqp/execution/providers/ibm.py @@ -102,10 +102,10 @@ def compute_expectation_value( qiskit_observables: list[SparsePauliOp] = [] for obs in job.measure.observables: - if obs.transpile is None: + if obs.pre_transpile is None: translated = obs.to_other_language(Language.QISKIT) else: - translated = obs.transpile + translated = obs.pre_transpile if TYPE_CHECKING: assert isinstance(translated, SparsePauliOp) qiskit_observables.append(translated) @@ -562,8 +562,8 @@ def submit_remote_ibm(job: Job) -> tuple[str, "RuntimeJobV2"]: qiskit_observables = [ ( obs.to_other_language(Language.QISKIT) - if obs.transpile is None - else obs.transpile + if obs.pre_transpile is None + else obs.pre_transpile ) for obs in meas.observables ] From de2e44ec66677fcf8eeff1a9b504a99c363acc6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 14 Aug 2025 12:01:55 +0000 Subject: [PATCH 04/25] chore: Files formated --- mpqp/core/instruction/measurement/measure.py | 2 +- mpqp/execution/runner.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mpqp/core/instruction/measurement/measure.py b/mpqp/core/instruction/measurement/measure.py index a6481cb2..92cb09e6 100644 --- a/mpqp/core/instruction/measurement/measure.py +++ b/mpqp/core/instruction/measurement/measure.py @@ -65,7 +65,7 @@ def __init__( @property def pre_measure(self) -> list[Gate]: """ - List of gates added before the measurement to correctly swap target qubits + List of gates added before the measurement to correctly swap target qubits when needed, or to change the basis for a ``BasisMeasure``. """ return [] diff --git a/mpqp/execution/runner.py b/mpqp/execution/runner.py index 94a42a95..55589530 100644 --- a/mpqp/execution/runner.py +++ b/mpqp/execution/runner.py @@ -87,6 +87,7 @@ def adjust_measure(measure: ExpectationMeasure, circuit: QCircuit): from mpqp.core.instruction.measurement.pauli_string import ( pauli_string_with_atom, ) + print("ok") pauli = ( pauli_string_with_atom(n_before) From 2564cd0172ae82bda99708cf789c283d00531aa4 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:47:34 +0200 Subject: [PATCH 05/25] refactor: update Braket observable not handling paulistring --- .../measurement/expectation_value.py | 20 ++++++++++--------- .../instruction/measurement/pauli_string.py | 17 ++++++++-------- mpqp/execution/providers/aws.py | 7 ++++++- mpqp/execution/runner.py | 2 +- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 673a6d8e..7c9c0056 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -310,15 +310,17 @@ def to_other_language( return QLMObservable(self.nb_qubits, matrix=self.matrix) elif language == Language.BRAKET: - if self._pauli_string: - return self.pauli_string.to_other_language(Language.BRAKET) - else: - from braket.circuits.observables import Hermitian - - return Hermitian( - self.matrix, - display_name=self.label if self.label is not None else "Hermitian", - ) + # TODO: Braket do not handle pauli with coef because it use QASM2 + # We need to pass without coef and compute yourself + # if self._pauli_string: + # return self.pauli_string.to_other_language(Language.BRAKET) + # else: + from braket.circuits.observables import Hermitian + + return Hermitian( + self.matrix, + display_name=self.label if self.label is not None else "Hermitian", + ) elif language == Language.CIRQ: return self.pauli_string.to_other_language(Language.CIRQ, circuit) else: diff --git a/mpqp/core/instruction/measurement/pauli_string.py b/mpqp/core/instruction/measurement/pauli_string.py index 4960a474..f5c1360e 100644 --- a/mpqp/core/instruction/measurement/pauli_string.py +++ b/mpqp/core/instruction/measurement/pauli_string.py @@ -755,15 +755,15 @@ def to_other_language( elif language == Language.MY_QLM: return [mono.to_other_language(language) for mono in self.monomials] elif language == Language.BRAKET: - pauli_string = None + from braket.circuits.observables import Sum + + pauli_string = [] + for mono in self.monomials: braket_mono = mono.to_other_language(Language.BRAKET) - pauli_string = ( - pauli_string + braket_mono - if pauli_string is not None - else braket_mono - ) - return pauli_string + pauli_string.append(braket_mono) + + return Sum(pauli_string) elif language == Language.CIRQ: cirq_pauli_string = None for monomial in self.monomials: @@ -1083,8 +1083,9 @@ def to_other_language( atom.to_other_language(Language.BRAKET) for atom in self.atoms # pyright: ignore[reportAssignmentType] ] + from braket.circuits.observables import TensorProduct - return self.coef * reduce(matmul, braket_atoms) + return self.coef * TensorProduct(braket_atoms) elif language == Language.CIRQ: from cirq.devices.line_qubit import LineQubit diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 59c30d65..fbc811b6 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -238,11 +238,16 @@ def run_braket_observable(job: Job): for obs in job.measure.observables: from copy import deepcopy + from braket.circuits.observables import Sum copy = deepcopy(transpiled_circuit) braket_obs = obs.to_other_language(Language.BRAKET) + if isinstance(braket_obs, Sum): + targets = [job.measure.targets] * len(braket_obs.summands) + else: + targets = job.measure.targets copy.expectation( # pyright: ignore[reportAttributeAccessIssue] - observable=braket_obs, target=job.measure.targets + observable=braket_obs, target=targets ) job.status = JobStatus.RUNNING local_result = device.run( diff --git a/mpqp/execution/runner.py b/mpqp/execution/runner.py index 94a42a95..653b3c6c 100644 --- a/mpqp/execution/runner.py +++ b/mpqp/execution/runner.py @@ -87,7 +87,7 @@ def adjust_measure(measure: ExpectationMeasure, circuit: QCircuit): from mpqp.core.instruction.measurement.pauli_string import ( pauli_string_with_atom, ) - print("ok") + pauli = ( pauli_string_with_atom(n_before) @ obs.pauli_string From e3092f819b653e1dd7b9a4b23b6047d0fae8362c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Oct 2025 13:46:21 +0000 Subject: [PATCH 06/25] chore: Files formated --- mpqp/core/instruction/measurement/measure.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mpqp/core/instruction/measurement/measure.py b/mpqp/core/instruction/measurement/measure.py index b3bb4735..a207fc4b 100644 --- a/mpqp/core/instruction/measurement/measure.py +++ b/mpqp/core/instruction/measurement/measure.py @@ -65,11 +65,11 @@ def __init__( @property def pre_measure(self) -> list[Gate]: """ - List of gates inserted before the measurement, either to correctly + List of gates inserted before the measurement, either to correctly swap target qubits when required or to adjust the basis for a BasisMeasure. - These operations ensure that the only measurement performed at the end - of the circuit is a standard measurement in the computational basis, - with qubits arranged in ascending order. This list may, for example, + These operations ensure that the only measurement performed at the end + of the circuit is a standard measurement in the computational basis, + with qubits arranged in ascending order. This list may, for example, include SWAP gates and U3 gates. """ return [] From 7f16626a0c4c494231edda9367e1c914cbe46916 Mon Sep 17 00:00:00 2001 From: Muhammad Attallah Date: Tue, 28 Oct 2025 00:13:58 +0100 Subject: [PATCH 07/25] feat: run with DirectReservation logic --- mpqp/execution/connection/aws_connection.py | 18 ++++++ mpqp/execution/providers/aws.py | 69 ++++++++++++++++----- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/mpqp/execution/connection/aws_connection.py b/mpqp/execution/connection/aws_connection.py index 147b9577..34bf751f 100644 --- a/mpqp/execution/connection/aws_connection.py +++ b/mpqp/execution/connection/aws_connection.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, Any, Optional, Union from termcolor import colored + from mpqp.environment.typechecked import conditional_typechecked if TYPE_CHECKING: @@ -122,6 +123,17 @@ def setup_aws_braket_account() -> tuple[str, list[Any]]: ) +def set_reservation_arn_if_needed() -> None: + """Optionally set and save a Braket reservation ARN if needed for specific runs. + This step is not required for normal usage.""" + if not get_env_variable("BRAKET_RESERVATION_ARN"): + reservation_arn = input( + "Enter your AWS Braket reservation ARN if provided (optional, press Enter to skip): " + ).strip() + if reservation_arn: + save_env_variable("BRAKET_RESERVATION_ARN", reservation_arn) + + def update_aws_credentials_file( profile_name: str, access_key_id: str, @@ -198,6 +210,9 @@ def configure_account_iam() -> tuple[str, list[Any]]: save_env_variable("BRAKET_AUTH_METHOD", "IAM") save_env_variable("BRAKET_CONFIGURED", "True") + + set_reservation_arn_if_needed() + return "IAM configuration successful.", [] @@ -260,6 +275,9 @@ def configure_account_sso() -> tuple[str, list[Any]]: save_env_variable("BRAKET_AUTH_METHOD", "SSO") save_env_variable("BRAKET_CONFIGURED", "True") + + set_reservation_arn_if_needed() + return "SSO configuration successful.", [] diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index e834717f..125d9a52 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -1,10 +1,9 @@ import math +from contextlib import contextmanager from typing import TYPE_CHECKING, Optional import numpy as np -from mpqp.environment.typechecked import conditional_typechecked -from mpqp.core.languages import Language from mpqp.core.circuit import QCircuit from mpqp.core.instruction.gates import CRk from mpqp.core.instruction.measurement import ( @@ -12,6 +11,8 @@ ExpectationMeasure, Observable, ) +from mpqp.core.languages import Language +from mpqp.environment.typechecked import conditional_typechecked from mpqp.execution.connection.aws_connection import get_braket_device from mpqp.execution.devices import AWSDevice from mpqp.execution.job import Job, JobStatus, JobType @@ -20,6 +21,7 @@ from mpqp.tools.errors import AWSBraketRemoteExecutionError, DeviceJobIncompatibleError if TYPE_CHECKING: + from braket.aws import AwsDevice from braket.circuits import Circuit from braket.tasks import GateModelQuantumTaskResult, QuantumTask @@ -88,7 +90,7 @@ def apply_noise_to_braket_circuit( @conditional_typechecked -def run_braket(job: Job) -> Result: +def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: """Executes the job on the right AWS Braket device (local or remote) precised in the job in parameter and waits until the task is completed, then returns the Result. @@ -112,11 +114,16 @@ def run_braket(job: Job) -> Result: f"{job.device} instead" ) + if reservation_arn is None: + from mpqp.environment.env_manager import get_env_variable + + reservation_arn = get_env_variable("BRAKET_RESERVATION_ARN") + from braket.tasks import GateModelQuantumTaskResult if isinstance(job.measure, ExpectationMeasure): - return run_braket_observable(job) - _, task = submit_job_braket(job) + return run_braket_observable(job, reservation_arn) + _, task = submit_job_braket(job, reservation_arn) res = task.result() if TYPE_CHECKING: assert isinstance(res, GateModelQuantumTaskResult) @@ -125,7 +132,7 @@ def run_braket(job: Job) -> Result: @conditional_typechecked -def run_braket_observable(job: Job): +def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): """Returns the result of an ``OBSERVABLE`` job. TODO: check that the link bellow is correctly generated. @@ -144,6 +151,11 @@ def run_braket_observable(job: Job): from braket.circuits import Circuit from braket.tasks import GateModelQuantumTaskResult + if reservation_arn is None: + from mpqp.environment.env_manager import get_env_variable + + reservation_arn = get_env_variable("BRAKET_RESERVATION_ARN") + assert isinstance(job.device, AWSDevice) if job.circuit.transpiled_circuit is None: transpiled_circuit = job.circuit.to_other_device(job.device) @@ -158,8 +170,8 @@ def run_braket_observable(job: Job): if job.measure is None: raise NotImplementedError("job.measure is None") assert isinstance(job.measure, ExpectationMeasure) - results = {} - errors = {} + + results, errors = {}, {} if job.measure.optimize_measurement: grouping = job.measure.get_pauli_grouping() from mpqp.tools.pauli_grouping import ( @@ -232,9 +244,10 @@ def run_braket_observable(job: Job): observable=braket_obs, target=job.measure.targets ) job.status = JobStatus.RUNNING - local_result = device.run( - copy, shots=job.measure.shots, inputs=None - ).result() + with optional_reservation_arn(device, reservation_arn): + local_result = device.run( + copy, shots=job.measure.shots, inputs=None + ).result() assert isinstance(local_result, GateModelQuantumTaskResult) results.update({f"observable_{len(results)}": local_result.values[0].real}) errors.update({f"observable_{len(errors)}": None}) @@ -244,7 +257,9 @@ def run_braket_observable(job: Job): @conditional_typechecked -def submit_job_braket(job: Job) -> tuple[str, "QuantumTask"]: +def submit_job_braket( + job: Job, reservation_arn: Optional[str] = None +) -> tuple[str, "QuantumTask"]: """Submits the job to the right local/remote device and returns the generated task. @@ -271,6 +286,12 @@ def submit_job_braket(job: Job) -> tuple[str, "QuantumTask"]: "`job` must correspond to an `AWSDevice`, but corresponds to a " f"{job.device} instead" ) + + if reservation_arn is None: + from mpqp.environment.env_manager import get_env_variable + + reservation_arn = get_env_variable("BRAKET_RESERVATION_ARN") + if job.job_type == JobType.STATE_VECTOR and job.device.is_remote(): raise DeviceJobIncompatibleError( "State vector cannot be computed using AWS Braket remote simulators" @@ -303,13 +324,17 @@ def submit_job_braket(job: Job) -> tuple[str, "QuantumTask"]: if job.job_type == JobType.STATE_VECTOR: braket_circuit.state_vector() # pyright: ignore[reportAttributeAccessIssue] job.status = JobStatus.RUNNING - task = device.run(braket_circuit, shots=0, inputs=None) + with optional_reservation_arn(device, reservation_arn): + task = device.run(braket_circuit, shots=0, inputs=None) elif job.job_type == JobType.SAMPLE: if TYPE_CHECKING: assert job.measure is not None job.status = JobStatus.RUNNING - task = device.run(braket_circuit, shots=job.measure.shots, inputs=None) + if TYPE_CHECKING: + assert isinstance(device, AwsDevice) + with optional_reservation_arn(device, reservation_arn): + task = device.run(braket_circuit, shots=job.measure.shots, inputs=None) elif job.job_type == JobType.OBSERVABLE: # TODO : [multi-obs] update this to take into account the case when we have list of Observables @@ -324,7 +349,8 @@ def submit_job_braket(job: Job) -> tuple[str, "QuantumTask"]: ) job.status = JobStatus.RUNNING - task = device.run(braket_circuit, shots=job.measure.shots, inputs=None) + with optional_reservation_arn(device, reservation_arn): + task = device.run(braket_circuit, shots=job.measure.shots, inputs=None) else: raise NotImplementedError(f"Job of type {job.job_type} not handled.") @@ -530,3 +556,16 @@ def estimate_cost_single_job( else: return 0 + + +@contextmanager +def optional_reservation_arn( + device: "AwsDevice", reservation_arn: Optional[str] = None +): + from braket.aws import DirectReservation + + if reservation_arn: + with DirectReservation(device, reservation_arn=reservation_arn): + yield + else: + yield From cf9779996b559d2ed325bc5f918814f87c28f523 Mon Sep 17 00:00:00 2001 From: Henri de Boutray <133855265+Henri-ColibrITD@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:30:24 +0100 Subject: [PATCH 08/25] Update mpqp/core/instruction/gates/custom_gate.py --- mpqp/core/instruction/gates/custom_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpqp/core/instruction/gates/custom_gate.py b/mpqp/core/instruction/gates/custom_gate.py index ce732462..cd3c606a 100644 --- a/mpqp/core/instruction/gates/custom_gate.py +++ b/mpqp/core/instruction/gates/custom_gate.py @@ -138,7 +138,7 @@ def to_other_language( if not printing: raise ValueError( "Custom gates defined with symbolic variables cannot be " - "exported to Braket yet (Braket requires numeric matrices)." + "exported to Braket for now (only numerical matrices are supported)." ) return None else: From 99182abf92ab99b3617689d10587b37cf89ffe7a Mon Sep 17 00:00:00 2001 From: Henri de Boutray <133855265+Henri-ColibrITD@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:31:11 +0100 Subject: [PATCH 09/25] Update mpqp/core/instruction/measurement/expectation_value.py --- mpqp/core/instruction/measurement/expectation_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 18bd89cd..7e0f5881 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -308,7 +308,7 @@ def to_other_language( return QLMObservable(self.nb_qubits, matrix=self.matrix) elif language == Language.BRAKET: - # TODO: Braket do not handle pauli with coef because it use QASM2 + # TODO: Braket does not handle pauli with coef because it uses QASM2 # We need to pass without coef and compute yourself # if self._pauli_string: # return self.pauli_string.to_other_language(Language.BRAKET) From 89ba320ff7569f644acd582f570c48e2df6e9138 Mon Sep 17 00:00:00 2001 From: Henri de Boutray <133855265+Henri-ColibrITD@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:42:24 +0100 Subject: [PATCH 10/25] Apply suggestion from @Henri-ColibrITD --- mpqp/core/instruction/measurement/pauli_string.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpqp/core/instruction/measurement/pauli_string.py b/mpqp/core/instruction/measurement/pauli_string.py index c7a0a21a..d0decf70 100644 --- a/mpqp/core/instruction/measurement/pauli_string.py +++ b/mpqp/core/instruction/measurement/pauli_string.py @@ -1390,7 +1390,7 @@ def commutes_with( f"Expected a PauliStringAtom in parameter but got {type(other).__name__}" ) if method == CommutingTypes.FULL: - return other.label == "I" or self.label == "I" or self.label == other.label + return other is pI or self is pI or self is other raise ValueError( f"PauliStringAtoms can only fully commutes with each others, instead received {method}" ) From 8333dfa628a13c69d8dba307d6a4ab62e0eb9781 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 4 Nov 2025 09:54:58 +0100 Subject: [PATCH 11/25] refactor: replace pauli_string_from_str with PauliString.from_str in ExpectationMeasure --- mpqp/core/instruction/measurement/expectation_value.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 18bd89cd..6980dbec 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -512,9 +512,6 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: return pauli_grouping_greedy(unique_monos, self.commuting_type) elif self.grouping_method == GroupingMethods.QISKIT: from qiskit.quantum_info import PauliList - from mpqp.core.instruction.measurement.pauli_string import ( - pauli_string_from_str, - ) pauli_labels = [mono.name.replace("@", "") for mono in unique_monos] pauli_list = PauliList(pauli_labels) @@ -526,7 +523,7 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: grouped = pauli_list.group_commuting() grouped_monomials = [ - [pauli_string_from_str(mono.to_label()) for mono in pauli] + [PauliString.from_str(mono.to_label()) for mono in pauli] for pauli in grouped ] From ee3382c4ff2fcca76d7447b13113f2764184ad99 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 4 Nov 2025 09:59:39 +0100 Subject: [PATCH 12/25] feat: add _sympy_to_braket_param function for parameter conversion --- mpqp/core/instruction/gates/native_gates.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index e28ef0a7..fc05573c 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -84,6 +84,23 @@ def _qiskit_parameter_adder( qiskit_param = param return qiskit_param +def _sympy_to_braket_param(val: Expr | float) -> "float | FreeParameter": + from sympy import Expr, Symbol + from braket.circuits import FreeParameter + + if isinstance(val, Symbol): + return FreeParameter(str(val)) + elif isinstance(val, Expr): + if val.free_symbols: + return FreeParameter(str(val)) # note: Braket won't parse expressions + else: + try: + return float(val.evalf()) # pyright: ignore[reportArgumentType] + except Exception as e: + raise ValueError(f"Failed to evaluate sympy expression '{val}': {e}") + else: + return float(val) + class NativeGate(Gate, SimpleClassReprABC): """The standard on which we rely, OpenQASM, comes with a set of gates From d539467b0b2a55003aebb467de80aac5c3b777c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Nov 2025 08:59:54 +0000 Subject: [PATCH 13/25] chore: Files formated --- mpqp/core/instruction/gates/native_gates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index fc05573c..1cd1ff0f 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -84,6 +84,7 @@ def _qiskit_parameter_adder( qiskit_param = param return qiskit_param + def _sympy_to_braket_param(val: Expr | float) -> "float | FreeParameter": from sympy import Expr, Symbol from braket.circuits import FreeParameter From 4fbbf5478f830810ed88dcb69729c2a3d9bfacd4 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:10:52 +0100 Subject: [PATCH 14/25] refactor: improve exception handling and clean up unused code in circuit and measurement modules --- mpqp/core/circuit.py | 8 ++++---- mpqp/core/instruction/gates/native_gates.py | 2 -- mpqp/core/instruction/measurement/expectation_value.py | 1 - tests/core/instruction/gates/test_custom_gate.py | 3 --- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/mpqp/core/circuit.py b/mpqp/core/circuit.py index 03c78ece..8fde992b 100644 --- a/mpqp/core/circuit.py +++ b/mpqp/core/circuit.py @@ -1406,10 +1406,10 @@ def to_other_language( if isinstance(instruction, ControlledGate): target = instruction.controls + target braket_circuit.add_instruction(braket_instr, target=target) - except: - print(braket_instr) - print(type(braket_instr)) - raise + except Exception as e: + raise ValueError( + f"{type(braket_instr)}{braket_instr} cannot be added to the braket circuit" + ) from e if len(self.noises) == 0: return braket_circuit diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index fc05573c..229ca6b0 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -88,8 +88,6 @@ def _sympy_to_braket_param(val: Expr | float) -> "float | FreeParameter": from sympy import Expr, Symbol from braket.circuits import FreeParameter - if isinstance(val, Symbol): - return FreeParameter(str(val)) elif isinstance(val, Expr): if val.free_symbols: return FreeParameter(str(val)) # note: Braket won't parse expressions diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 02732a08..a98d4493 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -307,7 +307,6 @@ def to_other_language( return QLMObservable(self.nb_qubits, matrix=self.matrix) elif language == Language.BRAKET: # TODO: Braket does not handle pauli with coef because it uses QASM2 - # We need to pass without coef and compute yourself # if self._pauli_string: # return self.pauli_string.to_other_language(Language.BRAKET) # else: diff --git a/tests/core/instruction/gates/test_custom_gate.py b/tests/core/instruction/gates/test_custom_gate.py index cc62cd82..9af397fa 100644 --- a/tests/core/instruction/gates/test_custom_gate.py +++ b/tests/core/instruction/gates/test_custom_gate.py @@ -158,11 +158,8 @@ def _test_execution_equivalence( ) targets = [position for _, position in gates_n_positions] - print(matrix) result_custom_gate = run(QCircuit([CustomGate(matrix, targets)]), device) result_circuit = run(circuit, device) - print(result_circuit) - print(result_custom_gate) assert matrix_eq( result_custom_gate.amplitudes, result_circuit.amplitudes, 1e-4, 1e-4 ) From d08a4963d7add4ebd3900a461ec266a64e535826 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:54:07 +0100 Subject: [PATCH 15/25] refactor: enhance error messages and clean up unused imports in various modules --- mpqp/core/circuit.py | 4 ++-- mpqp/core/instruction/gates/native_gates.py | 4 ++-- .../measurement/expectation_value.py | 4 ++-- .../instruction/measurement/pauli_string.py | 19 +++++++++++++++---- mpqp/execution/providers/aws.py | 4 +++- .../instruction/gates/test_custom_gate.py | 1 - tests/examples/test_demonstrations.py | 3 --- tests/execution/test_validity.py | 1 - 8 files changed, 24 insertions(+), 16 deletions(-) diff --git a/mpqp/core/circuit.py b/mpqp/core/circuit.py index 8fde992b..2d9d2108 100644 --- a/mpqp/core/circuit.py +++ b/mpqp/core/circuit.py @@ -1408,8 +1408,8 @@ def to_other_language( braket_circuit.add_instruction(braket_instr, target=target) except Exception as e: raise ValueError( - f"{type(braket_instr)}{braket_instr} cannot be added to the braket circuit" - ) from e + f"{type(braket_instr)}{braket_instr} cannot be added to the braket circuit: {e}" + ) if len(self.noises) == 0: return braket_circuit diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index c6ba0448..809e2214 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -86,10 +86,10 @@ def _qiskit_parameter_adder( def _sympy_to_braket_param(val: Expr | float) -> "float | FreeParameter": - from sympy import Expr, Symbol + from sympy import Expr from braket.circuits import FreeParameter - elif isinstance(val, Expr): + if isinstance(val, Expr): if val.free_symbols: return FreeParameter(str(val)) # note: Braket won't parse expressions else: diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index a98d4493..f451c688 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -509,7 +509,7 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: elif self.grouping_method == GroupingMethods.QISKIT: from qiskit.quantum_info import PauliList - pauli_labels = [mono.name.replace("@", "") for mono in unique_monos] + pauli_labels = [mono.short_name for mono in unique_monos] pauli_list = PauliList(pauli_labels) # Choose grouping based on commutativity type @@ -519,7 +519,7 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: grouped = pauli_list.group_commuting() grouped_monomials = [ - [PauliString.from_str(mono.to_label()) for mono in pauli] + [PauliString.from_str(mono.to_label()) for mono in pauli] # pyright: ignore[reportAttributeAccessIssue] for pauli in grouped ] diff --git a/mpqp/core/instruction/measurement/pauli_string.py b/mpqp/core/instruction/measurement/pauli_string.py index 7ca911a5..01bc834b 100644 --- a/mpqp/core/instruction/measurement/pauli_string.py +++ b/mpqp/core/instruction/measurement/pauli_string.py @@ -10,7 +10,7 @@ from enum import Enum, auto from functools import reduce from numbers import Real -from operator import matmul, mul +from operator import mul from typing import TYPE_CHECKING, Any, Literal, Optional, Union import numpy as np @@ -137,7 +137,7 @@ def from_str(compact_str: str, dict_value: Optional[dict[str, Coef]] = None): pattern = re.compile(r'([^IXYZ]+)?([IXYZ]+)') - monomials = [] + monomials: list[PauliStringMonomial] = [] for match in pattern.finditer(compact_str.replace(" ", "")): coef_str, atoms_str = match.groups() @@ -162,7 +162,8 @@ def from_str(compact_str: str, dict_value: Optional[dict[str, Coef]] = None): monomials.append( PauliStringMonomial(coef, [atoms_dict[atom] for atom in atoms_str]) ) - + if len(monomials) == 1: + return monomials[0] return PauliString(monomials) def _non_null_str(self): @@ -964,6 +965,10 @@ def __deepcopy__(self, memo: Optional[dict[int, Any]] = None): def name(self) -> str: return f"{'@'.join(map(str, self.atoms))}" + @property + def short_name(self) -> str: + return f"{''.join(atom.label for atom in self.atoms)}" + def __str__(self): from sympy import Expr @@ -1182,7 +1187,13 @@ def to_other_language( ] from braket.circuits.observables import TensorProduct - return self.coef * TensorProduct(braket_atoms) + if len(braket_atoms) == 1: + return ( + self.coef * braket_atoms[0] + ) # pyright: ignore[reportOperatorIssue] + return self.coef * TensorProduct( + braket_atoms + ) # pyright: ignore[reportOperatorIssue] elif language == Language.CIRQ: from cirq.devices.line_qubit import LineQubit diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index f1e46d5b..6477e0ba 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -176,7 +176,9 @@ def run_braket_observable(job: Job): ] else: - eigenvalues, transpiled_pre_measures = job.measure.pre_transpile + eigenvalues, transpiled_pre_measures = ( + job.measure.pre_transpile + ) # pyright: ignore[reportGeneralTypeIssues] expectation_values = {} for eigenvalues, pre_measure in zip(eigenvalues, transpiled_pre_measures): diff --git a/tests/core/instruction/gates/test_custom_gate.py b/tests/core/instruction/gates/test_custom_gate.py index 9af397fa..790d474c 100644 --- a/tests/core/instruction/gates/test_custom_gate.py +++ b/tests/core/instruction/gates/test_custom_gate.py @@ -1,4 +1,3 @@ -import contextlib import random from functools import reduce from itertools import product diff --git a/tests/examples/test_demonstrations.py b/tests/examples/test_demonstrations.py index 86cbfefd..749cefe9 100644 --- a/tests/examples/test_demonstrations.py +++ b/tests/examples/test_demonstrations.py @@ -1,5 +1,3 @@ -from typing import Any, Callable - import numpy as np import pytest from braket.devices import LocalSimulator @@ -15,7 +13,6 @@ QCircuit, run, ) -from mpqp.execution import AvailableDevice from mpqp.gates import * from mpqp.qasm.qasm_to_braket import qasm3_to_braket_Circuit from mpqp.tools.errors import UnsupportedBraketFeaturesWarning diff --git a/tests/execution/test_validity.py b/tests/execution/test_validity.py index 0caaddc3..46ae255a 100644 --- a/tests/execution/test_validity.py +++ b/tests/execution/test_validity.py @@ -1,4 +1,3 @@ -import contextlib from copy import deepcopy import numpy as np From 75da43237de2845dd68356909b17bae1d7703ffd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Nov 2025 09:54:24 +0000 Subject: [PATCH 16/25] chore: Files formated --- mpqp/core/instruction/measurement/expectation_value.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index f451c688..8ba2a4f3 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -519,7 +519,9 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: grouped = pauli_list.group_commuting() grouped_monomials = [ - [PauliString.from_str(mono.to_label()) for mono in pauli] # pyright: ignore[reportAttributeAccessIssue] + [ + PauliString.from_str(mono.to_label()) for mono in pauli + ] # pyright: ignore[reportAttributeAccessIssue] for pauli in grouped ] From b3056c6e45320235de4d58a53286deeb075a8bd2 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:58:22 +0100 Subject: [PATCH 17/25] refactor: remove unused import of contextlib in test_basis.py --- tests/core/instruction/measurement/test_basis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/instruction/measurement/test_basis.py b/tests/core/instruction/measurement/test_basis.py index 2f609d3c..e6daacba 100644 --- a/tests/core/instruction/measurement/test_basis.py +++ b/tests/core/instruction/measurement/test_basis.py @@ -1,4 +1,3 @@ -import contextlib from itertools import product import numpy as np From 00f753ffd3f621a86b9baaf99124a7da3ebd08ba Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:01:51 +0100 Subject: [PATCH 18/25] refactor: improve formatting of list comprehension in ExpectationMeasure class --- mpqp/core/instruction/measurement/expectation_value.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 8ba2a4f3..4b488c34 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -520,8 +520,9 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: grouped_monomials = [ [ - PauliString.from_str(mono.to_label()) for mono in pauli - ] # pyright: ignore[reportAttributeAccessIssue] + PauliString.from_str(mono.to_label()) # pyright: ignore[reportAttributeAccessIssue] + for mono in pauli + ] for pauli in grouped ] From be33d49a486944c7d90e5719b26e3a833e38d054 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Nov 2025 10:02:07 +0000 Subject: [PATCH 19/25] chore: Files formated --- mpqp/core/instruction/measurement/expectation_value.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 4b488c34..263ed050 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -520,8 +520,10 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: grouped_monomials = [ [ - PauliString.from_str(mono.to_label()) # pyright: ignore[reportAttributeAccessIssue] - for mono in pauli + PauliString.from_str( + mono.to_label() + ) # pyright: ignore[reportAttributeAccessIssue] + for mono in pauli ] for pauli in grouped ] From e1478d08979393b8bb010bcbf00b3e518fe70b9a Mon Sep 17 00:00:00 2001 From: Muhammad Attallah Date: Tue, 18 Nov 2025 11:42:44 +0100 Subject: [PATCH 20/25] feat: modify DirectReservation handling logic --- mpqp/execution/connection/aws_connection.py | 15 ---------- mpqp/execution/providers/aws.py | 33 ++++++++------------- mpqp/execution/runner.py | 11 +++++-- 3 files changed, 21 insertions(+), 38 deletions(-) diff --git a/mpqp/execution/connection/aws_connection.py b/mpqp/execution/connection/aws_connection.py index 34bf751f..a984f5d2 100644 --- a/mpqp/execution/connection/aws_connection.py +++ b/mpqp/execution/connection/aws_connection.py @@ -123,17 +123,6 @@ def setup_aws_braket_account() -> tuple[str, list[Any]]: ) -def set_reservation_arn_if_needed() -> None: - """Optionally set and save a Braket reservation ARN if needed for specific runs. - This step is not required for normal usage.""" - if not get_env_variable("BRAKET_RESERVATION_ARN"): - reservation_arn = input( - "Enter your AWS Braket reservation ARN if provided (optional, press Enter to skip): " - ).strip() - if reservation_arn: - save_env_variable("BRAKET_RESERVATION_ARN", reservation_arn) - - def update_aws_credentials_file( profile_name: str, access_key_id: str, @@ -211,8 +200,6 @@ def configure_account_iam() -> tuple[str, list[Any]]: save_env_variable("BRAKET_AUTH_METHOD", "IAM") save_env_variable("BRAKET_CONFIGURED", "True") - set_reservation_arn_if_needed() - return "IAM configuration successful.", [] @@ -276,8 +263,6 @@ def configure_account_sso() -> tuple[str, list[Any]]: save_env_variable("BRAKET_AUTH_METHOD", "SSO") save_env_variable("BRAKET_CONFIGURED", "True") - set_reservation_arn_if_needed() - return "SSO configuration successful.", [] diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 125d9a52..d18604a3 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -21,7 +21,6 @@ from mpqp.tools.errors import AWSBraketRemoteExecutionError, DeviceJobIncompatibleError if TYPE_CHECKING: - from braket.aws import AwsDevice from braket.circuits import Circuit from braket.tasks import GateModelQuantumTaskResult, QuantumTask @@ -114,11 +113,6 @@ def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: f"{job.device} instead" ) - if reservation_arn is None: - from mpqp.environment.env_manager import get_env_variable - - reservation_arn = get_env_variable("BRAKET_RESERVATION_ARN") - from braket.tasks import GateModelQuantumTaskResult if isinstance(job.measure, ExpectationMeasure): @@ -151,11 +145,6 @@ def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): from braket.circuits import Circuit from braket.tasks import GateModelQuantumTaskResult - if reservation_arn is None: - from mpqp.environment.env_manager import get_env_variable - - reservation_arn = get_env_variable("BRAKET_RESERVATION_ARN") - assert isinstance(job.device, AWSDevice) if job.circuit.transpiled_circuit is None: transpiled_circuit = job.circuit.to_other_device(job.device) @@ -244,6 +233,9 @@ def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): observable=braket_obs, target=job.measure.targets ) job.status = JobStatus.RUNNING + + if TYPE_CHECKING: + assert isinstance(device, AWSDevice) with optional_reservation_arn(device, reservation_arn): local_result = device.run( copy, shots=job.measure.shots, inputs=None @@ -287,11 +279,6 @@ def submit_job_braket( f"{job.device} instead" ) - if reservation_arn is None: - from mpqp.environment.env_manager import get_env_variable - - reservation_arn = get_env_variable("BRAKET_RESERVATION_ARN") - if job.job_type == JobType.STATE_VECTOR and job.device.is_remote(): raise DeviceJobIncompatibleError( "State vector cannot be computed using AWS Braket remote simulators" @@ -324,6 +311,9 @@ def submit_job_braket( if job.job_type == JobType.STATE_VECTOR: braket_circuit.state_vector() # pyright: ignore[reportAttributeAccessIssue] job.status = JobStatus.RUNNING + + if TYPE_CHECKING: + assert isinstance(device, AWSDevice) with optional_reservation_arn(device, reservation_arn): task = device.run(braket_circuit, shots=0, inputs=None) @@ -332,7 +322,7 @@ def submit_job_braket( assert job.measure is not None job.status = JobStatus.RUNNING if TYPE_CHECKING: - assert isinstance(device, AwsDevice) + assert isinstance(device, AWSDevice) with optional_reservation_arn(device, reservation_arn): task = device.run(braket_circuit, shots=job.measure.shots, inputs=None) @@ -349,6 +339,9 @@ def submit_job_braket( ) job.status = JobStatus.RUNNING + + if TYPE_CHECKING: + assert isinstance(device, AWSDevice) with optional_reservation_arn(device, reservation_arn): task = device.run(braket_circuit, shots=job.measure.shots, inputs=None) @@ -559,13 +552,11 @@ def estimate_cost_single_job( @contextmanager -def optional_reservation_arn( - device: "AwsDevice", reservation_arn: Optional[str] = None -): +def optional_reservation_arn(device: AWSDevice, reservation_arn: Optional[str] = None): from braket.aws import DirectReservation if reservation_arn: - with DirectReservation(device, reservation_arn=reservation_arn): + with DirectReservation(device.get_arn(), reservation_arn=reservation_arn): yield else: yield diff --git a/mpqp/execution/runner.py b/mpqp/execution/runner.py index 54c6532f..4871300e 100644 --- a/mpqp/execution/runner.py +++ b/mpqp/execution/runner.py @@ -209,6 +209,7 @@ def _run_single( device: AvailableDevice, values: Optional[dict[Expr | str, Complex]] = None, display_breakpoints: bool = True, + reservation_arn: Optional[str] = None, ) -> Result: """Runs the circuit on the ``backend``. If the circuit depends on variables, the ``values`` given in parameters are used to do the substitution. @@ -275,7 +276,7 @@ def _run_single( elif isinstance(device, ATOSDevice): return run_atos(job) elif isinstance(device, AWSDevice): - return run_braket(job) + return run_braket(job, reservation_arn=reservation_arn) elif isinstance(device, GOOGLEDevice): return run_google(job) elif isinstance(device, AZUREDevice): @@ -290,6 +291,7 @@ def run( device: Sequence[AvailableDevice], values: Optional[dict[Expr | str, Complex]] = None, display_breakpoints: bool = True, + reservation_arn: Optional[str] = None, ) -> BatchResult: ... @@ -299,6 +301,7 @@ def run( device: OneOrMany[AvailableDevice], values: Optional[dict[Expr | str, Complex]] = None, display_breakpoints: bool = True, + reservation_arn: Optional[str] = None, ) -> BatchResult: ... @@ -308,6 +311,7 @@ def run( device: AvailableDevice, values: Optional[dict[Expr | str, Complex]] = None, display_breakpoints: bool = True, + reservation_arn: Optional[str] = None, ) -> Result: ... @@ -317,6 +321,7 @@ def run( device: OneOrMany[AvailableDevice], values: Optional[dict[Expr | str, Complex]] = None, display_breakpoints: bool = True, + reservation_arn: Optional[str] = None, ) -> Result | BatchResult: """Runs the circuit on the backend, or list of backend, provided in parameter. @@ -401,6 +406,7 @@ def namer(circ: QCircuit, i: int): dev, values, display_breakpoints, + reservation_arn, ) for i, circ in enumerate(flatten(circuit)) for dev in flatten(device) @@ -415,6 +421,7 @@ def submit( circuit: QCircuit, device: AvailableDevice, values: Optional[dict[Expr | str, Complex]] = None, + reservation_arn: Optional[str] = None, ) -> tuple[str, Job]: """Submit the job related to the circuit on the remote backend provided in parameter. The submission returns a ``job_id`` that can be used to retrieve @@ -462,7 +469,7 @@ def submit( elif isinstance(device, ATOSDevice): job_id, _ = submit_QLM(job) elif isinstance(device, AWSDevice): - job_id, _ = submit_job_braket(job) + job_id, _ = submit_job_braket(job, reservation_arn=reservation_arn) elif isinstance(device, AZUREDevice): job_id, _ = submit_job_azure(job) else: From 19cbbb71e717767bb8af7baeb7bde6e0acd3d51a Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:44:55 +0100 Subject: [PATCH 21/25] refactor: remove conditional_typechecked decorator from AWS Braket functions --- mpqp/execution/providers/aws.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 95fd92fa..2f1fd514 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -12,7 +12,6 @@ Observable, ) from mpqp.core.languages import Language -from mpqp.environment.typechecked import conditional_typechecked from mpqp.execution.connection.aws_connection import get_braket_device from mpqp.execution.devices import AWSDevice from mpqp.execution.job import Job, JobStatus, JobType @@ -86,8 +85,6 @@ def apply_noise_to_braket_circuit( return noisy_circuit - -@conditional_typechecked def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: """Executes the job on the right AWS Braket device (local or remote) precised in the job in parameter and waits until the task is completed, then @@ -124,7 +121,6 @@ def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: return extract_result(res, job, job.device) -@conditional_typechecked def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): """Returns the result of an ``OBSERVABLE`` job. @@ -267,7 +263,6 @@ def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): return Result(job, results, errors, job.measure.shots) -@conditional_typechecked def submit_job_braket( job: Job, reservation_arn: Optional[str] = None ) -> tuple[str, "QuantumTask"]: From d3717d7d7f32ab15132c47217e76f1115d154435 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Nov 2025 14:45:15 +0000 Subject: [PATCH 22/25] chore: Files formated --- mpqp/execution/providers/aws.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 2f1fd514..591a888e 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -85,6 +85,7 @@ def apply_noise_to_braket_circuit( return noisy_circuit + def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: """Executes the job on the right AWS Braket device (local or remote) precised in the job in parameter and waits until the task is completed, then From 011013c6f2000317c446bb8827197f1ef67ef7fb Mon Sep 17 00:00:00 2001 From: Muhammad Attallah Date: Tue, 25 Nov 2025 17:23:58 +0100 Subject: [PATCH 23/25] fix: type errors --- mpqp/core/instruction/measurement/expectation_value.py | 4 ++-- mpqp/core/instruction/measurement/pauli_string.py | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 263ed050..4ab496b9 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -521,8 +521,8 @@ def get_pauli_grouping(self) -> list[list[PauliStringMonomial]]: grouped_monomials = [ [ PauliString.from_str( - mono.to_label() - ) # pyright: ignore[reportAttributeAccessIssue] + mono.to_label() # pyright: ignore[reportAttributeAccessIssue] + ) for mono in pauli ] for pauli in grouped diff --git a/mpqp/core/instruction/measurement/pauli_string.py b/mpqp/core/instruction/measurement/pauli_string.py index 01bc834b..05e62d6c 100644 --- a/mpqp/core/instruction/measurement/pauli_string.py +++ b/mpqp/core/instruction/measurement/pauli_string.py @@ -1025,10 +1025,6 @@ def __imul__(self, other: "Coef") -> PauliStringMonomial: self.coef = new_coef return self - res = deepcopy(self) - res *= other - return res - def __itruediv__(self, other: "Coef") -> PauliStringMonomial: new_coef: "Coef" = ( self.coef / other From 2bb3c2f1f7878689b054996e5e8493c268077957 Mon Sep 17 00:00:00 2001 From: Muhammad Attallah Date: Wed, 26 Nov 2025 10:28:51 +0100 Subject: [PATCH 24/25] chore: update direct resrvation using aws braket device --- mpqp/execution/providers/aws.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 591a888e..9d9a16f2 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -237,6 +237,7 @@ def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): for obs in job.measure.observables: from copy import deepcopy + from braket.circuits.observables import Sum copy = deepcopy(transpiled_circuit) @@ -581,7 +582,8 @@ def optional_reservation_arn(device: AWSDevice, reservation_arn: Optional[str] = from braket.aws import DirectReservation if reservation_arn: - with DirectReservation(device.get_arn(), reservation_arn=reservation_arn): + braket_device = get_braket_device(device) + with DirectReservation(braket_device, reservation_arn=reservation_arn): yield else: yield From 5d8e814fbbd4ac4173a53715e55a71748e1fcc13 Mon Sep 17 00:00:00 2001 From: Muhammad Attallah Date: Tue, 9 Dec 2025 00:10:11 +0100 Subject: [PATCH 25/25] chore: replace resrvation_arn with provider options dict --- mpqp/execution/providers/aws.py | 17 ++++++++++++----- mpqp/execution/runner.py | 26 ++++++++++++++++---------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/mpqp/execution/providers/aws.py b/mpqp/execution/providers/aws.py index 9d9a16f2..6654f5a4 100644 --- a/mpqp/execution/providers/aws.py +++ b/mpqp/execution/providers/aws.py @@ -86,7 +86,7 @@ def apply_noise_to_braket_circuit( return noisy_circuit -def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: +def run_braket(job: Job, **provider_options) -> Result: """Executes the job on the right AWS Braket device (local or remote) precised in the job in parameter and waits until the task is completed, then returns the Result. @@ -102,6 +102,8 @@ def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: This function is not meant to be used directly, please use :func:`~mpqp.execution.runner.run` instead. """ + reservation_arn: Optional[str] = provider_options.get("reservation_arn") + # TODO : [multi-obs] update this to take into account the case when we have list of Observables # TODO : [multi-obs] check if Braket allows for a list of several observables if not isinstance(job.device, AWSDevice): @@ -113,8 +115,8 @@ def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: from braket.tasks import GateModelQuantumTaskResult if isinstance(job.measure, ExpectationMeasure): - return run_braket_observable(job, reservation_arn) - _, task = submit_job_braket(job, reservation_arn) + return run_braket_observable(job, **provider_options) + _, task = submit_job_braket(job, **provider_options) res = task.result() if TYPE_CHECKING: assert isinstance(res, GateModelQuantumTaskResult) @@ -122,7 +124,7 @@ def run_braket(job: Job, reservation_arn: Optional[str] = None) -> Result: return extract_result(res, job, job.device) -def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): +def run_braket_observable(job: Job, **provider_options): """Returns the result of an ``OBSERVABLE`` job. TODO: check that the link bellow is correctly generated. @@ -141,6 +143,8 @@ def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): from braket.circuits import Circuit from braket.tasks import GateModelQuantumTaskResult + reservation_arn: Optional[str] = provider_options.get("reservation_arn") + assert isinstance(job.device, AWSDevice) if job.circuit.transpiled_circuit is None: transpiled_circuit = job.circuit.to_other_device(job.device) @@ -266,7 +270,8 @@ def run_braket_observable(job: Job, reservation_arn: Optional[str] = None): def submit_job_braket( - job: Job, reservation_arn: Optional[str] = None + job: Job, + **provider_options, ) -> tuple[str, "QuantumTask"]: """Submits the job to the right local/remote device and returns the generated task. @@ -289,6 +294,8 @@ def submit_job_braket( This function is not meant to be used directly, please use :func:`~mpqp.execution.runner.run` instead. """ + reservation_arn: Optional[str] = provider_options.get("reservation_arn") + if not isinstance(job.device, AWSDevice): raise ValueError( "`job` must correspond to an `AWSDevice`, but corresponds to a " diff --git a/mpqp/execution/runner.py b/mpqp/execution/runner.py index 5ff0f2dd..626fad84 100644 --- a/mpqp/execution/runner.py +++ b/mpqp/execution/runner.py @@ -206,7 +206,7 @@ def _run_single( device: AvailableDevice, values: "Optional[dict[Expr | str, Complex]]" = None, display_breakpoints: bool = True, - reservation_arn: Optional[str] = None, + provider_options: Optional[dict] = None, ) -> Result: """Runs the circuit on the ``backend``. If the circuit depends on variables, the ``values`` given in parameters are used to do the substitution. @@ -241,6 +241,8 @@ def _run_single( Error: None """ + provider_options = provider_options or {} + from mpqp.execution.simulated_devices import ( SimulatedDevice, StaticIBMSimulatedDevice, @@ -273,7 +275,7 @@ def _run_single( elif isinstance(device, ATOSDevice): return run_atos(job) elif isinstance(device, AWSDevice): - return run_braket(job, reservation_arn=reservation_arn) + return run_braket(job, **provider_options) elif isinstance(device, GOOGLEDevice): return run_google(job) elif isinstance(device, AZUREDevice): @@ -288,7 +290,7 @@ def run( device: Sequence[AvailableDevice], values: "Optional[dict[Expr | str, Complex]]" = None, display_breakpoints: bool = True, - reservation_arn: Optional[str] = None, + provider_options: Optional[dict] = None, ) -> BatchResult: ... @@ -298,7 +300,7 @@ def run( device: OneOrMany[AvailableDevice], values: "Optional[dict[Expr | str, Complex]]" = None, display_breakpoints: bool = True, - reservation_arn: Optional[str] = None, + provider_options: Optional[dict] = None, ) -> BatchResult: ... @@ -308,7 +310,7 @@ def run( device: AvailableDevice, values: "Optional[dict[Expr | str, Complex]]" = None, display_breakpoints: bool = True, - reservation_arn: Optional[str] = None, + provider_options: Optional[dict] = None, ) -> Result: ... @@ -317,7 +319,7 @@ def run( device: OneOrMany[AvailableDevice], values: "Optional[dict[Expr | str, Complex]]" = None, display_breakpoints: bool = True, - reservation_arn: Optional[str] = None, + provider_options: Optional[dict] = None, ) -> Result | BatchResult: """Runs the circuit on the backend, or list of backend, provided in parameter. @@ -402,21 +404,23 @@ def namer(circ: QCircuit, i: int): dev, values, display_breakpoints, - reservation_arn, + provider_options, ) for i, circ in enumerate(flatten(circuit)) for dev in flatten(device) ] ) else: - return _run_single(circuit, device, values, display_breakpoints) + return _run_single( + circuit, device, values, display_breakpoints, provider_options + ) def submit( circuit: QCircuit, device: AvailableDevice, values: Optional[dict[Expr | str, Complex]] = None, - reservation_arn: Optional[str] = None, + provider_options: Optional[dict] = None, ) -> tuple[str, Job]: """Submit the job related to the circuit on the remote backend provided in parameter. The submission returns a ``job_id`` that can be used to retrieve @@ -449,6 +453,8 @@ def submit( Note: Unlike :func:`run`, you can only submit on one device at a time. """ + provider_options = provider_options or {} + if values is None: values = {} if not device.is_remote(): @@ -464,7 +470,7 @@ def submit( elif isinstance(device, ATOSDevice): job_id, _ = submit_QLM(job) elif isinstance(device, AWSDevice): - job_id, _ = submit_job_braket(job, reservation_arn=reservation_arn) + job_id, _ = submit_job_braket(job, **provider_options) elif isinstance(device, AZUREDevice): job_id, _ = submit_job_azure(job) else: