DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
dbscan.cpp
Go to the documentation of this file.
3
4#include <cassert>
5#include <limits>
6
7namespace triggeralgs {
8namespace dbscan {
9
10//======================================================================
11int
12neighbours_sorted(const std::vector<Hit*>& hits, Hit& q, float eps, int minPts)
13{
14 int n = 0;
15 // Loop over the hits starting from the latest hit, since we will
16 // ~always be adding a hit at recent times
17 for (auto hit_it = hits.rbegin(); hit_it != hits.rend(); ++hit_it) {
18 if ((*hit_it)->time > q.time + eps)
19 continue;
20 if ((*hit_it)->time < q.time - eps)
21 break;
22
23 if (q.add_potential_neighbour(*hit_it, eps, minPts))
24 ++n;
25 }
26 return n;
27}
28
29//======================================================================
30bool
31Cluster::maybe_add_new_hit(Hit* new_hit, float eps, int minPts)
32{
33 // Should we add this hit?
34 bool do_add = false;
35
36 // Hits earlier than new_hit time minus `eps` can't possibly be
37 // neighbours, so start the search there in the sorted list of hits in this
38 // cluster
39 auto begin_it = std::lower_bound(
40 hits.begin(), hits.end(), new_hit->time - eps, time_comp_lower);
41
42 for (auto it = begin_it; it != hits.end(); ++it) {
43 Hit* h = *it;
44 if (h->add_potential_neighbour(new_hit, eps, minPts)) {
45 do_add = true;
46 if (h->neighbours.size() + 1 >= minPts) {
48 } else {
50 }
51 }
52 } // end loop over hits in cluster
53
54 if (do_add) {
55 add_hit(new_hit);
56 }
57
58 return do_add;
59}
60
61//======================================================================
62void
64{
65 hits.insert(h);
66 h->cluster = index;
67 latest_time = std::max(latest_time, h->time);
71 }
72}
73
74//======================================================================
75void
77{
78 // TODO: it might be faster to do some sort of explicit "merge" of the hits,
79 // eg:
80 //
81 // this->hits.insert(other hits); // Inserts at end
82 // std::inplace_merge(...)
83 //
84 // This might save some reallocations of the vector
85 for (auto h : other.hits) {
86 assert(h);
87 add_hit(h);
88 }
89 other.hits.clear();
91}
92
93//======================================================================
94void
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
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) {
118 seedSet.insert(
119 seedSet.end(), q->neighbours.begin(), q->neighbours.end());
120 }
121 }
122}
123
124//======================================================================
125void
126IncrementalDBSCAN::add_point(float time, float channel, std::vector<Cluster>* completed_clusters)
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}
134
135//======================================================================
136void
137IncrementalDBSCAN::add_primitive(const triggeralgs::TriggerPrimitive& prim, std::vector<Cluster>* completed_clusters)
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}
150
151//======================================================================
152void
153IncrementalDBSCAN::add_hit(Hit* new_hit, std::vector<Cluster>* completed_clusters)
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;
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;
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;
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) {
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}
293
294void
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}
318
319}
320}
321// Local Variables:
322// mode: c++
323// c-basic-offset: 4
324// c-file-style: "linux"
325// End:
std::vector< Hit * >::iterator begin()
Definition Hit.hpp:59
void insert(Hit *h)
Definition Hit.cpp:16
std::vector< Hit * >::iterator end()
Definition Hit.hpp:60
size_t size() const
Definition Hit.hpp:67
void add_primitive(const triggeralgs::TriggerPrimitive &prim, std::vector< Cluster > *completed_clusters=nullptr)
Definition dbscan.cpp:137
void add_point(float time, float channel, std::vector< Cluster > *completed_clusters=nullptr)
Definition dbscan.cpp:126
void cluster_reachable(Hit *seed_hit, Cluster &cluster)
Definition dbscan.cpp:95
void add_hit(Hit *new_hit, std::vector< Cluster > *completed_clusters=nullptr)
Definition dbscan.cpp:153
std::map< int, Cluster > m_clusters
Definition dbscan.hpp:102
const int kUndefined
Definition Hit.hpp:15
bool time_comp_lower(const Hit *hit, const float t)
Definition Hit.hpp:120
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
A single energy deposition on a TPC or PDS channel.
bool maybe_add_new_hit(Hit *new_hit, float eps, int minPts)
Definition dbscan.cpp:31
void steal_hits(Cluster &other)
Definition dbscan.cpp:76
bool add_potential_neighbour(Hit *other, float eps, int minPts)
Definition Hit.cpp:60
void reset(float _time, int _chan, const triggeralgs::TriggerPrimitive *_prim=nullptr)
Definition Hit.cpp:44
Connectedness connectedness
Definition Hit.hpp:84