From 3a29086fa4ab51c977fe9f8a2b08e3311aa50075 Mon Sep 17 00:00:00 2001 From: huaweil Date: Tue, 9 Jun 2026 06:32:08 +0000 Subject: [PATCH 1/2] [python] Fix dynamics target evolve_async Bind the callable async evolution path used by the dynamics target and propagate callable failures through the returned future. Signed-off-by: huaweil --- python/runtime/cudaq/algorithms/py_evolve.cpp | 10 +++ python/tests/dynamics/test_evolve_dynamics.py | 78 +++++++++++++++++++ runtime/cudaq/algorithms/evolve_internal.h | 7 +- 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/python/runtime/cudaq/algorithms/py_evolve.cpp b/python/runtime/cudaq/algorithms/py_evolve.cpp index afe86714dbf..4303a8327d0 100644 --- a/python/runtime/cudaq/algorithms/py_evolve.cpp +++ b/python/runtime/cudaq/algorithms/py_evolve.cpp @@ -281,6 +281,15 @@ pyEvolveAsync(state initial_state, std::vector kernels, nanobind::arg("noise_model") = std::nullopt, \ nanobind::arg("shots_count") = -1); +#define DEFINE_ASYNC_CALLABLE_OVERLOAD(pyMod) \ + pyMod.def( \ + "evolve_async", \ + [](std::function evolveFunctor, std::size_t qpu_id) { \ + return detail::evolve_async(std::move(evolveFunctor), qpu_id); \ + }, \ + "Asynchronously execute a callable that returns an evolution result.", \ + nanobind::arg("evolve_function"), nanobind::arg("qpu_id") = 0); + /// @brief Bind the evolve cudaq function for circuit simulator void bindPyEvolve(nanobind::module_ &mod) { // Sync evolve overloads @@ -298,6 +307,7 @@ void bindPyEvolve(nanobind::module_ &mod) { DEFINE_ASYNC_PARAM_TYPE_OVERLOAD(long, mod); DEFINE_ASYNC_PARAM_TYPE_OVERLOAD(double, mod); DEFINE_ASYNC_PARAM_TYPE_OVERLOAD(std::complex, mod); + DEFINE_ASYNC_CALLABLE_OVERLOAD(mod); } } // namespace cudaq diff --git a/python/tests/dynamics/test_evolve_dynamics.py b/python/tests/dynamics/test_evolve_dynamics.py index c2f13bd7a2b..5bd10e7e72a 100644 --- a/python/tests/dynamics/test_evolve_dynamics.py +++ b/python/tests/dynamics/test_evolve_dynamics.py @@ -84,6 +84,84 @@ def test_euler_integrator(): np.testing.assert_allclose(expected_answer, expt, 1e-3) +def test_evolve_async_dynamics_target(): + """Test async evolution on the dynamics target.""" + steps = np.linspace(0, 0.1, 3) + hamiltonian = operators.number(0) + dimensions = {0: 2} + save_all = cudaq.IntermediateResultSave.ALL + save_expectations = cudaq.IntermediateResultSave.EXPECTATION_VALUE + psi0_ = cp.zeros(2, dtype=cp.complex128) + psi0_[1] = 1.0 + psi0 = cudaq.State.from_data(psi0_) + + expected = cudaq.evolve(hamiltonian, dimensions, Schedule(steps, ["t"]), + psi0) + evolution_result = cudaq.evolve_async(hamiltonian, dimensions, + Schedule(steps, ["t"]), psi0).get() + + assert len(evolution_result.intermediate_states()) == 1 + np.testing.assert_allclose(np.array(evolution_result.final_state()), + np.array(expected.final_state()), + atol=1e-12) + + expected = cudaq.evolve(hamiltonian, + dimensions, + Schedule(steps, ["t"]), + psi0, + store_intermediate_results=save_all) + evolution_result = cudaq.evolve_async( + hamiltonian, + dimensions, + Schedule(steps, ["t"]), + psi0, + store_intermediate_results=save_all).get() + + assert len(evolution_result.intermediate_states()) == len(steps) + np.testing.assert_allclose(np.array(evolution_result.final_state()), + np.array(expected.final_state()), + atol=1e-12) + + expected = cudaq.evolve(hamiltonian, + dimensions, + Schedule(steps, ["t"]), + psi0, + observables=[hamiltonian], + store_intermediate_results=save_expectations) + evolution_result = cudaq.evolve_async( + hamiltonian, + dimensions, + Schedule(steps, ["t"]), + psi0, + observables=[hamiltonian], + store_intermediate_results=save_expectations).get() + + assert len(evolution_result.intermediate_states()) == 1 + assert len(evolution_result.expectation_values()) == len(steps) + expected_values = [[obs.expectation() + for obs in step] + for step in expected.expectation_values()] + actual_values = [[obs.expectation() + for obs in step] + for step in evolution_result.expectation_values()] + np.testing.assert_allclose(actual_values, expected_values, atol=1e-12) + + +def test_evolve_async_dynamics_target_propagates_errors(): + """Test async dynamics errors are reported through get().""" + steps = np.linspace(0, 0.1, 3) + schedule = Schedule(steps, ["t"]) + hamiltonian = operators.number(0) + psi0_ = cp.zeros(2, dtype=cp.complex128) + psi0_[1] = 1.0 + psi0 = cudaq.State.from_data(psi0_) + + result = cudaq.evolve_async(hamiltonian, {1: 2}, schedule, psi0) + + with pytest.raises(Exception): + result.get() + + def test_save_all_intermediate_states(): """ Test save all option for intermediate states diff --git a/runtime/cudaq/algorithms/evolve_internal.h b/runtime/cudaq/algorithms/evolve_internal.h index cae26730bde..8da382bfa83 100644 --- a/runtime/cudaq/algorithms/evolve_internal.h +++ b/runtime/cudaq/algorithms/evolve_internal.h @@ -16,6 +16,7 @@ #include "cudaq/platform.h" #include "cudaq/platform/QuantumExecutionQueue.h" #include "cudaq/schedule.h" +#include namespace cudaq { class base_integrator; @@ -164,7 +165,11 @@ evolve_async(std::function evolveFunctor, QuantumTask wrapped = detail::make_copyable_function( [p = std::move(promise), evolveFunctor]() mutable { - p.set_value(evolveFunctor()); + try { + p.set_value(evolveFunctor()); + } catch (...) { + p.set_exception(std::current_exception()); + } }); platform.enqueueAsyncTask(qpu_id, wrapped); From 194317be6f0ff5bd250a161418223a07a3892efb Mon Sep 17 00:00:00 2001 From: huaweil Date: Wed, 10 Jun 2026 01:44:43 +0000 Subject: [PATCH 2/2] Propagate evolve async task failures Ensure kernel-based evolve_async overloads report worker exceptions through the returned future and clear noise state on failure. Signed-off-by: huaweil --- runtime/cudaq/algorithms/evolve_internal.h | 42 ++++++++++++++-------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/runtime/cudaq/algorithms/evolve_internal.h b/runtime/cudaq/algorithms/evolve_internal.h index 8da382bfa83..2a973956b75 100644 --- a/runtime/cudaq/algorithms/evolve_internal.h +++ b/runtime/cudaq/algorithms/evolve_internal.h @@ -109,13 +109,19 @@ evolve_async(state initial_state, QuantumKernel &&kernel, [p = std::move(promise), func = std::forward(kernel), initial_state, observables, noise_model, shots_count, &platform]() mutable { - if (noise_model.has_value()) - platform.set_noise(&noise_model.value()); - with_platform_in_library_mode libraryMode(platform); - auto result = evolve(initial_state, func, observables, shots_count); - if (noise_model.has_value()) - platform.set_noise(nullptr); - p.set_value(std::move(result)); + try { + if (noise_model.has_value()) + platform.set_noise(&noise_model.value()); + with_platform_in_library_mode libraryMode(platform); + auto result = evolve(initial_state, func, observables, shots_count); + if (noise_model.has_value()) + platform.set_noise(nullptr); + p.set_value(std::move(result)); + } catch (...) { + if (noise_model.has_value()) + platform.set_noise(nullptr); + p.set_exception(std::current_exception()); + } }); platform.enqueueAsyncTask(qpu_id, wrapped); @@ -136,14 +142,20 @@ evolve_async(state initial_state, std::vector kernels, QuantumTask wrapped = detail::make_copyable_function( [p = std::move(promise), kernels, initial_state, observables, noise_model, shots_count, &platform, save_intermediate_states]() mutable { - if (noise_model.has_value()) - platform.set_noise(&noise_model.value()); - with_platform_in_library_mode libraryMode(platform); - auto result = evolve(initial_state, kernels, observables, shots_count, - save_intermediate_states); - if (noise_model.has_value()) - platform.set_noise(nullptr); - p.set_value(std::move(result)); + try { + if (noise_model.has_value()) + platform.set_noise(&noise_model.value()); + with_platform_in_library_mode libraryMode(platform); + auto result = evolve(initial_state, kernels, observables, shots_count, + save_intermediate_states); + if (noise_model.has_value()) + platform.set_noise(nullptr); + p.set_value(std::move(result)); + } catch (...) { + if (noise_model.has_value()) + platform.set_noise(nullptr); + p.set_exception(std::current_exception()); + } }); platform.enqueueAsyncTask(qpu_id, wrapped);