CUDA: C++17 cleanup of ClusterFinderCUDA
Build on RHEL8 / build (push) Successful in 3m58s
Build on RHEL9 / build (push) Successful in 4m8s
Run tests using data on local RHEL8 / build (push) Failing after 5m13s

- std::array for per-slot state; std::exchange in BatchView::steal
- [[nodiscard]]/noexcept on the public API and release path
- cuda_check moved to aare::cuda::detail (was reserved __cuda_check)
- _v type traits; drop redundant std::move and `using namespace aare`
- Add pytest for BatchView slot release
This commit is contained in:
kferjaoui committed 2026-10-01 15:17:03 +02:00
1 parent 4d23b2b29c
commit 47d89446e2
6 files changed
+128 -48

No files matched your search

+1 -3
View File
@@ -14,8 +14,6 @@
namespace py = pybind11;
using pd_type = double;
using namespace aare;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -170,7 +168,7 @@ sqrt(max(sum2/n - mean^2, 0)). Counterpart to `noise` for the device pedestal.)"
.def(
"steal_clusters",
[](CF &self, bool realloc_same_capacity) {
return std::move(self.steal_clusters(realloc_same_capacity));
return self.steal_clusters(realloc_same_capacity);
},
py::arg("realloc_same_capacity") = true)
+1 -3
View File
@@ -13,8 +13,6 @@
namespace py = pybind11;
using pd_type = double;
using namespace aare;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -65,7 +63,7 @@ void define_ClusterFinderCUDAGraph(py::module &m, const std::string &typestr) {
.def(
"steal_clusters",
[](CF &self, bool realloc_same_capacity) {
return std::move(self.steal_clusters(realloc_same_capacity));
return self.steal_clusters(realloc_same_capacity);
},
py::arg("realloc_same_capacity") = true)
+80
View File
@@ -0,0 +1,80 @@
# SPDX-License-Identifier: MPL-2.0
import numpy as np
import pytest
from aare import ClusterFinderCUDA, _cuda_available, find_cluster_views_batched_iter
pytestmark = pytest.mark.skipif(
not _cuda_available(), reason="aare built without AARE_CUDA")
SHAPE = (32, 32)
BATCH = 4
# Pixel that gets a photon in every data frame, away from the border.
HIT = (16, 16)
@pytest.fixture
def cf():
cf = ClusterFinderCUDA(SHAPE, n_streams=2, min_pedestal_samples=10)
rng = np.random.default_rng(42)
for _ in range(20):
cf.push_pedestal_frame(
rng.normal(1000, 5, SHAPE).astype(np.uint16))
return cf
@pytest.fixture
def frames():
rng = np.random.default_rng(7)
f = rng.normal(1000, 5, (BATCH, *SHAPE)).astype(np.uint16)
f[:, HIT[0], HIT[1]] = 3000
return f
def test_collect_view_exposes_batch(cf, frames):
tok = cf.submit_batch(frames, first_frame=100)
with cf.collect_view(tok) as v:
assert v.valid
assert v.n_frames == BATCH
assert v.first_frame == 100
assert all(v.count(i) >= 1 for i in range(BATCH))
assert not v.valid
def test_released_view_frees_its_slot(cf, frames):
# Cycles both slots twice. pybind11 move-constructs every returned
# BatchView, so a move that drops the slot index leaves the slot held
# forever and the third submit_batch() throws.
for i in range(2 * 2):
tok = cf.submit_batch(frames, first_frame=i * BATCH)
v = cf.collect_view(tok)
v.release()
assert not v.valid
def test_dropped_view_frees_its_slot(cf, frames):
# The destructor releases, so a view that is never bound is harmless.
for i in range(2 * 2):
tok = cf.submit_batch(frames, first_frame=i * BATCH)
cf.collect_view(tok)
def test_held_view_blocks_its_slot(cf, frames):
held = cf.collect_view(cf.submit_batch(frames))
# The other slot is still free.
with cf.collect_view(cf.submit_batch(frames)):
pass
with pytest.raises(RuntimeError, match="held by a BatchView"):
cf.submit_batch(frames)
held.release()
with cf.collect_view(cf.submit_batch(frames)):
pass
def test_views_batched_iter_pipelines_all_chunks(cf, frames):
data = np.concatenate([frames] * 3)
seen = []
for v in find_cluster_views_batched_iter(cf, data, first_frame=10,
chunk=BATCH):
seen.append((v.first_frame, v.n_frames))
assert seen == [(10, BATCH), (10 + BATCH, BATCH), (10 + 2 * BATCH, BATCH)]