DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
triggeralgs::dbscan::IncrementalDBSCAN Class Reference

#include <dbscan.hpp>

Public Member Functions

 IncrementalDBSCAN (float eps, unsigned int minPts, size_t pool_size=100000)
 
void add_primitive (const triggeralgs::TriggerPrimitive &prim, std::vector< Cluster > *completed_clusters=nullptr)
 
void add_point (float time, float channel, std::vector< Cluster > *completed_clusters=nullptr)
 
void add_hit (Hit *new_hit, std::vector< Cluster > *completed_clusters=nullptr)
 
void trim_hits ()
 
std::vector< Hit * > get_hits () const
 
std::map< int, Clusterget_clusters () const
 
uint64_t get_first_prim_time () const
 

Private Member Functions

void cluster_reachable (Hit *seed_hit, Cluster &cluster)
 

Private Attributes

float m_eps
 
float m_minPts
 
std::vector< Hitm_hit_pool
 
size_t m_pool_begin
 
size_t m_pool_end
 
std::vector< Hit * > m_hits
 
float m_latest_time { 0 }
 
uint64_t m_first_prim_time {0}
 
std::map< int, Clusterm_clusters
 

Detailed Description

Definition at line 57 of file dbscan.hpp.

Constructor & Destructor Documentation

◆ IncrementalDBSCAN()

triggeralgs::dbscan::IncrementalDBSCAN::IncrementalDBSCAN ( float eps,
unsigned int minPts,
size_t pool_size = 100000 )
inline

Definition at line 60 of file dbscan.hpp.

61 : m_eps(eps)
62 , m_minPts(minPts)
63 , m_pool_begin(0)
64 , m_pool_end(0)
65 {
66 for(size_t i=0; i<pool_size; ++i){
67 m_hit_pool.emplace_back(0,0);
68 }
69 }

Member Function Documentation

◆ add_hit()

void triggeralgs::dbscan::IncrementalDBSCAN::add_hit ( Hit * new_hit,
std::vector< Cluster > * completed_clusters = nullptr )

Definition at line 153 of file dbscan.cpp.

154{
155 // TODO: this should be a member variable, not a static, in case
156 // there are multiple IncrementalDBSCAN instances
157 static int next_cluster_index = 0;
158
159 m_hits.push_back(new_hit);
160 m_latest_time = new_hit->time;
161
162 // All the clusters that this hit neighboured. If there are
163 // multiple clusters neighbouring this hit, we'll merge them at
164 // the end
165 std::set<int> clusters_neighbouring_hit;
166
167 // Find all the hit's neighbours
169
170 for (auto neighbour : new_hit->neighbours) {
171 if (neighbour->cluster != kUndefined && neighbour->cluster != kNoise &&
172 neighbour->neighbours.size() + 1 >= m_minPts) {
173 // This neighbour is a core point in a cluster. Add the cluster to the list of
174 // clusters that will contain this hit
175 clusters_neighbouring_hit.insert(neighbour->cluster);
176 }
177 }
178
179 if (clusters_neighbouring_hit.empty()) {
180 // This hit didn't match any existing cluster. See if we can
181 // make a new cluster out of it. Otherwise mark it as noise
182
183 if (new_hit->neighbours.size() + 1 >= m_minPts) {
184 // std::cout << "New cluster starting at hit time " << new_hit->time << " with " << new_hit->neighbours.size() << " neighbours" << std::endl;
185 new_hit->connectedness = Connectedness::kCore;
186 auto new_it = m_clusters.emplace_hint(
187 m_clusters.end(), next_cluster_index, next_cluster_index);
188 Cluster& new_cluster = new_it->second;
189 new_cluster.completeness = Completeness::kIncomplete;
190 new_cluster.add_hit(new_hit);
191 next_cluster_index++;
192 cluster_reachable(new_hit, new_cluster);
193 }
194 else{
195 // std::cout << "New hit time " << new_hit->time << " with " << new_hit->neighbours.size() << " neighbours is noise" << std::endl;
196 }
197 } else {
198 // This hit neighboured at least one cluster. Add the hit and
199 // its noise neighbours to the first cluster in the list, then
200 // merge the rest of the clusters into it
201
202 auto index_it = clusters_neighbouring_hit.begin();
203
204 auto it = m_clusters.find(*index_it);
205 assert(it != m_clusters.end());
206 Cluster& cluster = it->second;
207 // std::cout << "Adding hit time " << new_hit->time << " with " << new_hit->neighbours.size() << " neighbours to existing cluster" << std::endl;
208 cluster.add_hit(new_hit);
209
210 // TODO: this seems wrong: we're adding this hit's neighbours
211 // to the cluster even if this hit isn't a core point, but if
212 // I wrap the whole thing in "if(new_hit is core)" then the
213 // results differ from classic DBSCAN
214 for (auto q : new_hit->neighbours) {
215 if (q->cluster == kUndefined || q->cluster == kNoise) {
216 // std::cout << " Adding hit time " << q->time << " to existing cluster" << std::endl;
217 cluster.add_hit(q);
218 }
219 // If the neighbouring hit q has exactly m_minPts
220 // neighbours, it must have become a core point by the
221 // addition of new_hit. Add q's neighbours to the cluster
222 if(q->neighbours.size() + 1 == m_minPts){
223 for (auto r : q->neighbours) {
224 cluster.add_hit(r);
225 }
226 }
227 }
228
229
230 ++index_it;
231
232 for (; index_it != clusters_neighbouring_hit.end(); ++index_it) {
233
234 auto other_it = m_clusters.find(*index_it);
235 assert(other_it != m_clusters.end());
236 Cluster& other_cluster = other_it->second;
237 cluster.steal_hits(other_cluster);
238 }
239 }
240
241 // Last case: new_hit and its neighbour are both noise, but the
242 // addition of new_hit makes the neighbour a core point. So we
243 // start a new cluster at the neighbour, and walk out from there
244 for (auto& neighbour : new_hit->neighbours) {
245 if(neighbour->neighbours.size() + 1 >= m_minPts){
246 // std::cout << "new_hit's neighbour at " << neighbour->time << " has " << neighbour->neighbours.size() << " neighbours, so is core" << std::endl;
247 if(neighbour->cluster==kNoise || neighbour->cluster==kUndefined){
248 if(new_hit->cluster==kNoise || new_hit->cluster==kUndefined){
249 auto new_it = m_clusters.emplace_hint(
250 m_clusters.end(), next_cluster_index, next_cluster_index);
251 Cluster& new_cluster = new_it->second;
252 new_cluster.completeness = Completeness::kIncomplete;
253 new_cluster.add_hit(neighbour);
254 next_cluster_index++;
255 cluster_reachable(neighbour, new_cluster);
256 }
257 }
258 }
259 else {
260 // std::cout << "new_hit's neighbour at " << neighbour->time << " has " << neighbour->neighbours.size() << " neighbours, so is NOT core" << std::endl;
261 }
262 }
263
264
265 // Delete any completed clusters from the list. Put them in the
266 // `completed_clusters` vector, if that vector was passed
267 auto clust_it = m_clusters.begin();
268 while (clust_it != m_clusters.end()) {
269 Cluster& cluster = clust_it->second;
270
271 if (cluster.latest_time < m_latest_time - m_eps) {
272 cluster.completeness = Completeness::kComplete;
273 }
274
275 if (cluster.completeness == Completeness::kComplete) {
276 if(completed_clusters){
277 // TODO: room for std::move here?
278 //
279 // Clusters that got merged into another cluster had
280 // their hits cleared, and were set kComplete, by
281 // steal_hits
282 if(cluster.hits.size()!=0){
283 completed_clusters->push_back(cluster);
284 }
285 }
286 clust_it = m_clusters.erase(clust_it);
287 continue;
288 } else {
289 ++clust_it;
290 }
291 }
292}
void cluster_reachable(Hit *seed_hit, Cluster &cluster)
Definition dbscan.cpp:95
std::map< int, Cluster > m_clusters
Definition dbscan.hpp:102
const int kUndefined
Definition Hit.hpp:15
const int kNoise
Definition Hit.hpp:14
int neighbours_sorted(const std::vector< Hit * > &hits, Hit &q, float eps, int minPts)
Definition dbscan.cpp:12

◆ add_point()

void triggeralgs::dbscan::IncrementalDBSCAN::add_point ( float time,
float channel,
std::vector< Cluster > * completed_clusters = nullptr )

Definition at line 126 of file dbscan.cpp.

127{
128 Hit& new_hit=m_hit_pool[m_pool_end];
129 new_hit.reset(time, channel);
130 ++m_pool_end;
131 if(m_pool_end==m_hit_pool.size()) m_pool_end=0;
132 add_hit(&new_hit, completed_clusters);
133}
void add_hit(Hit *new_hit, std::vector< Cluster > *completed_clusters=nullptr)
Definition dbscan.cpp:153

◆ add_primitive()

void triggeralgs::dbscan::IncrementalDBSCAN::add_primitive ( const triggeralgs::TriggerPrimitive & prim,
std::vector< Cluster > * completed_clusters = nullptr )

Definition at line 137 of file dbscan.cpp.

138{
139 if(m_first_prim_time==0){
141 }
142
143 Hit& new_hit=m_hit_pool[m_pool_end];
144 new_hit.reset(1e-2*(prim.time_start-m_first_prim_time), prim.channel, &prim);
145 ++m_pool_end;
146 if(m_pool_end==m_hit_pool.size()) m_pool_end=0;
147
148 add_hit(&new_hit, completed_clusters);
149}

◆ cluster_reachable()

void triggeralgs::dbscan::IncrementalDBSCAN::cluster_reachable ( Hit * seed_hit,
Cluster & cluster )
private

Definition at line 95 of file dbscan.cpp.

96{
97 // Loop over all neighbours (and the neighbours of core points, and so on)
98 std::vector<Hit*> seedSet(seed_hit->neighbours.begin(),
99 seed_hit->neighbours.end());
100
101 while (!seedSet.empty()) {
102 Hit* q = seedSet.back();
103 seedSet.pop_back();
104 // Change noise to a border point
105 if (q->connectedness == Connectedness::kNoise) {
106 cluster.add_hit(q);
107 }
108
109 if (q->cluster != kUndefined) {
110 continue;
111 }
112
113 cluster.add_hit(q);
114
115 // If q is a core point, add its neighbours to the search list
116 if (q->neighbours.size() + 1 >= m_minPts) {
117 q->connectedness = Connectedness::kCore;
118 seedSet.insert(
119 seedSet.end(), q->neighbours.begin(), q->neighbours.end());
120 }
121 }
122}

◆ get_clusters()

std::map< int, Cluster > triggeralgs::dbscan::IncrementalDBSCAN::get_clusters ( ) const
inline

Definition at line 83 of file dbscan.hpp.

83{ return m_clusters; }

◆ get_first_prim_time()

uint64_t triggeralgs::dbscan::IncrementalDBSCAN::get_first_prim_time ( ) const
inline

Definition at line 85 of file dbscan.hpp.

85{ return m_first_prim_time; }

◆ get_hits()

std::vector< Hit * > triggeralgs::dbscan::IncrementalDBSCAN::get_hits ( ) const
inline

Definition at line 81 of file dbscan.hpp.

81{ return m_hits; }

◆ trim_hits()

void triggeralgs::dbscan::IncrementalDBSCAN::trim_hits ( )

Definition at line 295 of file dbscan.cpp.

296{
297 // Find the earliest time of a hit in any cluster in the list (active or
298 // not)
299 float earliest_time = std::numeric_limits<float>::max();
300
301 for (auto& cluster : m_clusters) {
302 earliest_time =
303 std::min(earliest_time, (*cluster.second.hits.begin())->time);
304 }
305
306 // If there were no clusters, set the earliest_time to the latest time
307 // (otherwise it would still be FLOAT_MAX)
308 if (m_clusters.empty()) {
309 earliest_time = m_latest_time;
310 }
311 auto last_it = std::lower_bound(m_hits.begin(),
312 m_hits.end(),
313 earliest_time - 10 * m_eps,
315
316 m_hits.erase(m_hits.begin(), last_it);
317}
bool time_comp_lower(const Hit *hit, const float t)
Definition Hit.hpp:120

Member Data Documentation

◆ m_clusters

std::map<int, Cluster> triggeralgs::dbscan::IncrementalDBSCAN::m_clusters
private

Definition at line 102 of file dbscan.hpp.

◆ m_eps

float triggeralgs::dbscan::IncrementalDBSCAN::m_eps
private

Definition at line 94 of file dbscan.hpp.

◆ m_first_prim_time

uint64_t triggeralgs::dbscan::IncrementalDBSCAN::m_first_prim_time {0}
private

Definition at line 100 of file dbscan.hpp.

100{0};

◆ m_hit_pool

std::vector<Hit> triggeralgs::dbscan::IncrementalDBSCAN::m_hit_pool
private

Definition at line 96 of file dbscan.hpp.

◆ m_hits

std::vector<Hit*> triggeralgs::dbscan::IncrementalDBSCAN::m_hits
private

Definition at line 98 of file dbscan.hpp.

◆ m_latest_time

float triggeralgs::dbscan::IncrementalDBSCAN::m_latest_time { 0 }
private

Definition at line 99 of file dbscan.hpp.

99{ 0 }; // The latest time of a hit in the vector of hits

◆ m_minPts

float triggeralgs::dbscan::IncrementalDBSCAN::m_minPts
private

Definition at line 95 of file dbscan.hpp.

◆ m_pool_begin

size_t triggeralgs::dbscan::IncrementalDBSCAN::m_pool_begin
private

Definition at line 97 of file dbscan.hpp.

◆ m_pool_end

size_t triggeralgs::dbscan::IncrementalDBSCAN::m_pool_end
private

Definition at line 97 of file dbscan.hpp.


The documentation for this class was generated from the following files: