You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix sample_async noise model lifetime and isolation (#3857)
<!--
Thanks for helping us improve CUDA-Q!
⚠️ The pull request title should be concise and understandable for all.
⚠️ If your pull request fixes an open issue, please link to the issue.
Checklist:
- [ ] I have added tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->
### Description
<!-- Include relevant issues here, describe what changed and why -->
## Summary
This PR fixes `cudaq.sample_async(..., noise_model=...)` for local
simulators by ensuring the noise model is applied correctly during
asynchronous execution and does not leak into subsequent calls.
## Root cause
- The async implementation could reference a noise model whose lifetime
ended before the queued task executed.
- Noise configuration was not scoped to the async task lifetime, which
could cause state pollution.
- Noise set/reset needed to be applied per-QPU (using the provided
`qpu_id`).
## Fix
- Extend `details::runSamplingAsync` to accept an optional noise model
and capture it by value inside the async task.
- Set the noise model at the start of the async task and reset it on
completion (including exception paths) to prevent state leakage.
- Apply set/reset per-QPU using `qpu_id`.
- Reject non-empty noise models on remote platforms with a clear error.
- Update Python binding-side remote checks to respect the provided
`qpu_id`.
## Tests
Added regression tests in `python/tests/builder/test_NoiseModel.py`:
- `test_sample_async_with_noise`
- `test_sample_async_noise_isolation`
## How to repro
1.
```
import cudaq
cudaq.set_target("density-matrix-cpu")
cudaq.set_random_seed(42)
k = cudaq.make_kernel()
q = k.qalloc()
k.x(q)
k.mz(q)
noise = cudaq.NoiseModel()
noise.add_channel("x", [0], cudaq.DepolarizationChannel(1.0))
# Noise should be visible in async result.
noisy = cudaq.sample_async(k, shots_count=1000, noise_model=noise).get()
print("async noisy:", noisy)
# Subsequent calls without noise must remain clean (no state pollution).
clean = cudaq.sample(k, shots_count=200)
print("after clean:", clean)
assert clean.count("1") == 200
```
2.
does not always occur
```
import cudaq, gc
cudaq.set_target("density-matrix-cpu")
cudaq.set_random_seed(42)
k = cudaq.make_kernel()
q = k.qalloc()
k.x(q)
k.mz(q)
def launch_once():
# Noise model is intentionally scoped to this function.
noise = cudaq.NoiseModel()
noise.add_channel("x", [0], cudaq.DepolarizationChannel(1.0))
fut = cudaq.sample_async(k, shots_count=200, noise_model=noise)
return fut
futs = [launch_once() for _ in range(200)]
gc.collect() # Try to encourage destruction of temporaries
for f in futs:
_ = f.get()
print("done")
```
---------
Signed-off-by: huaweil <huaweil@nvidia.com>
Co-authored-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com>
Co-authored-by: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com>
0 commit comments