libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
pair.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include "arg_wrappers.hpp"
10#include "common.hpp"
11#include "introspect.hpp"
12#include "numeric_traits.hpp"
13#include "processor.hpp"
14#include "time_tagged_events.hpp"
15#include "vector_queue.hpp"
16
17#include <algorithm>
18#include <array>
19#include <bitset>
20#include <cstddef>
21#include <iterator>
22#include <optional>
23#include <stdexcept>
24#include <type_traits>
25#include <utility>
26
27namespace tcspc {
28
29namespace internal {
30
31template <std::size_t NStopChannels, typename NumericTraits,
32 typename Downstream>
33class pair_all {
34 static_assert(
35 processor<Downstream, std::array<detection_event<NumericTraits>, 2>>);
36
37 NumericTraits::channel_type start_chan;
38 std::array<typename NumericTraits::channel_type, NStopChannels> stop_chans;
39 NumericTraits::abstime_type window_size;
40
41 // Buffer all starts within time window.
42 internal::vector_queue<typename NumericTraits::abstime_type> starts;
43
44 Downstream downstream;
45
46 void expel_old_starts(NumericTraits::abstime_type earliest_stop) {
47 auto const cutoff = pairing_cutoff(earliest_stop, window_size);
48 while (not starts.empty() && starts.front() < cutoff)
49 starts.pop();
50 }
51
52 public:
53 explicit pair_all(
54 arg::start_channel<typename NumericTraits::channel_type> start_channel,
55 std::array<typename NumericTraits::channel_type, NStopChannels>
56 stop_channels,
57 arg::time_window<typename NumericTraits::abstime_type> time_window,
58 Downstream downstream)
59 : start_chan(start_channel.value), stop_chans(stop_channels),
60 window_size(time_window.value), downstream(std::move(downstream)) {
61 if (window_size < 0)
62 throw std::invalid_argument(
63 "pair_all time_window must not be negative");
64 }
65
66 [[nodiscard]] auto introspect_node() const -> processor_info {
67 return processor_info(this, "pair_all");
68 }
69
70 [[nodiscard]] auto introspect_graph() const -> processor_graph {
71 return downstream.introspect_graph().push_entry_point(this);
72 }
73
74 template <typename NT> void handle(detection_event<NT> const &event) {
75 static_assert(std::is_same_v<typename NT::abstime_type,
76 typename NumericTraits::abstime_type>);
77 static_assert(std::is_same_v<typename NT::channel_type,
78 typename NumericTraits::channel_type>);
79
80 expel_old_starts(event.abstime);
81 auto chan_index = std::distance(
82 stop_chans.cbegin(),
83 std::find(stop_chans.cbegin(), stop_chans.cend(), event.channel));
84 if (std::size_t(chan_index) < NStopChannels) {
85 starts.for_each([&](auto start_time) {
86 downstream.handle(
87 std::array<detection_event<NumericTraits>, 2>{
88 {{start_time, start_chan}, event}});
89 });
90 }
91 if (event.channel == start_chan)
92 starts.push(event.abstime);
93 downstream.handle(event);
94 }
95
96 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
97 template <typename NT> void handle(detection_event<NT> &&event) {
98 handle(static_cast<detection_event<NT> const &>(event));
99 }
100
101 template <typename OtherEvent>
102 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
103 void handle(OtherEvent &&event) {
104 downstream.handle(std::forward<OtherEvent>(event));
105 }
106
107 void flush() { downstream.flush(); }
108};
109
110template <std::size_t NStopChannels, typename NumericTraits,
111 typename Downstream>
112class pair_one {
113 static_assert(
114 processor<Downstream, std::array<detection_event<NumericTraits>, 2>>);
115
116 NumericTraits::channel_type start_chan;
117 std::array<typename NumericTraits::channel_type, NStopChannels> stop_chans;
118 NumericTraits::abstime_type window_size;
119
120 // Buffer all starts within time window, and mark stop channels that have
121 // been matched.
122 struct start_and_flags {
123 NumericTraits::abstime_type time;
124 std::bitset<NStopChannels> stopped;
125 };
126 internal::vector_queue<start_and_flags> starts;
127
128 Downstream downstream;
129
130 void expel_old_starts(NumericTraits::abstime_type earliest_stop) {
131 auto const cutoff = pairing_cutoff(earliest_stop, window_size);
132 while (not starts.empty() &&
133 (starts.front().time < cutoff || starts.front().stopped.all()))
134 starts.pop();
135 }
136
137 public:
138 explicit pair_one(
139 arg::start_channel<typename NumericTraits::channel_type> start_channel,
140 std::array<typename NumericTraits::channel_type, NStopChannels>
141 stop_channels,
142 arg::time_window<typename NumericTraits::abstime_type> time_window,
143 Downstream downstream)
144 : start_chan(start_channel.value), stop_chans(stop_channels),
145 window_size(time_window.value), downstream(std::move(downstream)) {
146 if (window_size < 0)
147 throw std::invalid_argument(
148 "pair_one time_window must not be negative");
149 }
150
151 [[nodiscard]] auto introspect_node() const -> processor_info {
152 return processor_info(this, "pair_one");
153 }
154
155 [[nodiscard]] auto introspect_graph() const -> processor_graph {
156 return downstream.introspect_graph().push_entry_point(this);
157 }
158
159 template <typename NT> void handle(detection_event<NT> const &event) {
160 static_assert(std::is_same_v<typename NT::abstime_type,
161 typename NumericTraits::abstime_type>);
162 static_assert(std::is_same_v<typename NT::channel_type,
163 typename NumericTraits::channel_type>);
164
165 expel_old_starts(event.abstime);
166 auto const chan_index = static_cast<std::size_t>(std::distance(
167 stop_chans.cbegin(),
168 std::find(stop_chans.cbegin(), stop_chans.cend(), event.channel)));
169 if (chan_index < NStopChannels) {
170 starts.for_each([&](start_and_flags &sf) {
171 if (not sf.stopped[chan_index]) {
172 downstream.handle(
173 std::array<detection_event<NumericTraits>, 2>{
174 {{sf.time, start_chan}, event}});
175 sf.stopped[chan_index] = true;
176 }
177 });
178 }
179 if (event.channel == start_chan)
180 starts.push(start_and_flags{event.abstime, {}});
181 downstream.handle(event);
182 }
183
184 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
185 template <typename NT> void handle(detection_event<NT> &&event) {
186 handle(static_cast<detection_event<NT> const &>(event));
187 }
188
189 template <typename OtherEvent>
190 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
191 void handle(OtherEvent &&event) {
192 downstream.handle(std::forward<OtherEvent>(event));
193 }
194
195 void flush() { downstream.flush(); }
196};
197
198template <std::size_t NStopChannels, typename NumericTraits,
199 typename Downstream>
200class pair_all_between {
201 static_assert(
202 processor<Downstream, std::array<detection_event<NumericTraits>, 2>>);
203
204 NumericTraits::channel_type start_chan;
205 std::array<typename NumericTraits::channel_type, NStopChannels> stop_chans;
206 NumericTraits::abstime_type window_size;
207
208 // Buffer the most recent start within the time window.
209 std::optional<typename NumericTraits::abstime_type> start;
210
211 Downstream downstream;
212
213 void expel_old_start(NumericTraits::abstime_type earliest_stop) {
214 auto const cutoff = pairing_cutoff(earliest_stop, window_size);
215 if (start.has_value() && *start < cutoff)
216 start = std::nullopt;
217 }
218
219 public:
220 explicit pair_all_between(
221 arg::start_channel<typename NumericTraits::channel_type> start_channel,
222 std::array<typename NumericTraits::channel_type, NStopChannels>
223 stop_channels,
224 arg::time_window<typename NumericTraits::abstime_type> time_window,
225 Downstream downstream)
226 : start_chan(start_channel.value), stop_chans(stop_channels),
227 window_size(time_window.value), downstream(std::move(downstream)) {
228 if (window_size < 0)
229 throw std::invalid_argument(
230 "pair_all_between time_window must not be negative");
231 }
232
233 [[nodiscard]] auto introspect_node() const -> processor_info {
234 return processor_info(this, "pair_all_between");
235 }
236
237 [[nodiscard]] auto introspect_graph() const -> processor_graph {
238 return downstream.introspect_graph().push_entry_point(this);
239 }
240
241 template <typename NT> void handle(detection_event<NT> const &event) {
242 static_assert(std::is_same_v<typename NT::abstime_type,
243 typename NumericTraits::abstime_type>);
244 static_assert(std::is_same_v<typename NT::channel_type,
245 typename NumericTraits::channel_type>);
246
247 expel_old_start(event.abstime);
248 if (start.has_value()) {
249 auto chan_index =
250 std::distance(stop_chans.cbegin(),
251 std::find(stop_chans.cbegin(), stop_chans.cend(),
252 event.channel));
253 if (std::size_t(chan_index) < NStopChannels) {
254 downstream.handle(
255 std::array<detection_event<NumericTraits>, 2>{
256 {{*start, start_chan}, event}});
257 }
258 }
259 if (event.channel == start_chan)
260 start = event.abstime;
261 downstream.handle(event);
262 }
263
264 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
265 template <typename NT> void handle(detection_event<NT> &&event) {
266 handle(static_cast<detection_event<NT> const &>(event));
267 }
268
269 template <typename OtherEvent>
270 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
271 void handle(OtherEvent &&event) {
272 downstream.handle(std::forward<OtherEvent>(event));
273 }
274
275 void flush() { downstream.flush(); }
276};
277
278template <std::size_t NStopChannels, typename NumericTraits,
279 typename Downstream>
280class pair_one_between {
281 static_assert(
282 processor<Downstream, std::array<detection_event<NumericTraits>, 2>>);
283
284 NumericTraits::channel_type start_chan;
285 std::array<typename NumericTraits::channel_type, NStopChannels> stop_chans;
286 NumericTraits::abstime_type window_size;
287
288 // Buffer the most recent start within the time window, and mark stop
289 // channels that have been matched.
290 struct start_and_flags {
291 NumericTraits::abstime_type time;
292 std::bitset<NStopChannels> stopped;
293 };
294 std::optional<start_and_flags> start;
295
296 Downstream downstream;
297
298 void expel_old_start(NumericTraits::abstime_type earliest_stop) {
299 auto const cutoff = pairing_cutoff(earliest_stop, window_size);
300 if (start.has_value() &&
301 (start->time < cutoff || start->stopped.all()))
302 start = std::nullopt;
303 }
304
305 public:
306 explicit pair_one_between(
307 arg::start_channel<typename NumericTraits::channel_type> start_channel,
308 std::array<typename NumericTraits::channel_type, NStopChannels>
309 stop_channels,
310 arg::time_window<typename NumericTraits::abstime_type> time_window,
311 Downstream downstream)
312 : start_chan(start_channel.value), stop_chans(stop_channels),
313 window_size(time_window.value), downstream(std::move(downstream)) {
314 if (window_size < 0)
315 throw std::invalid_argument(
316 "pair_one_between time_window must not be negative");
317 }
318
319 [[nodiscard]] auto introspect_node() const -> processor_info {
320 return processor_info(this, "pair_one_between");
321 }
322
323 [[nodiscard]] auto introspect_graph() const -> processor_graph {
324 return downstream.introspect_graph().push_entry_point(this);
325 }
326
327 template <typename NT> void handle(detection_event<NT> const &event) {
328 static_assert(std::is_same_v<typename NT::abstime_type,
329 typename NumericTraits::abstime_type>);
330 static_assert(std::is_same_v<typename NT::channel_type,
331 typename NumericTraits::channel_type>);
332
333 expel_old_start(event.abstime);
334 if (start.has_value()) {
335 auto const chan_index = static_cast<std::size_t>(
336 std::distance(stop_chans.cbegin(),
337 std::find(stop_chans.cbegin(), stop_chans.cend(),
338 event.channel)));
339 if (chan_index < NStopChannels && not start->stopped[chan_index]) {
340 downstream.handle(
341 std::array<detection_event<NumericTraits>, 2>{
342 {{start->time, start_chan}, event}});
343 start->stopped[chan_index] = true;
344 }
345 }
346 if (event.channel == start_chan)
347 start = start_and_flags{event.abstime, {}};
348 downstream.handle(event);
349 }
350
351 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
352 template <typename NT> void handle(detection_event<NT> &&event) {
353 handle(static_cast<detection_event<NT> const &>(event));
354 }
355
356 template <typename OtherEvent>
357 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
358 void handle(OtherEvent &&event) {
359 downstream.handle(std::forward<OtherEvent>(event));
360 }
361
362 void flush() { downstream.flush(); }
363};
364
365} // namespace internal
366
406template <std::size_t NStopChannels,
407 typename NumericTraits = default_numeric_traits, typename Downstream>
410 std::array<typename NumericTraits::channel_type, NStopChannels>
411 stop_channels,
413 Downstream downstream) {
414 return internal::pair_all<NStopChannels, NumericTraits, Downstream>(
415 start_channel, stop_channels, time_window, std::move(downstream));
416}
417
459template <std::size_t NStopChannels,
460 typename NumericTraits = default_numeric_traits, typename Downstream>
463 std::array<typename NumericTraits::channel_type, NStopChannels>
464 stop_channels,
466 Downstream downstream) {
467 return internal::pair_one<NStopChannels, NumericTraits, Downstream>(
468 start_channel, stop_channels, time_window, std::move(downstream));
469}
470
511template <std::size_t NStopChannels,
512 typename NumericTraits = default_numeric_traits, typename Downstream>
515 std::array<typename NumericTraits::channel_type, NStopChannels>
516 stop_channels,
518 Downstream downstream) {
519 return internal::pair_all_between<NStopChannels, NumericTraits,
520 Downstream>(
521 start_channel, stop_channels, time_window, std::move(downstream));
522}
523
565template <std::size_t NStopChannels,
566 typename NumericTraits = default_numeric_traits, typename Downstream>
569 std::array<typename NumericTraits::channel_type, NStopChannels>
570 stop_channels,
572 Downstream downstream) {
573 return internal::pair_one_between<NStopChannels, NumericTraits,
574 Downstream>(
575 start_channel, stop_channels, time_window, std::move(downstream));
576}
577
578} // namespace tcspc
auto pair_all_between(arg::start_channel< typename NumericTraits::channel_type > start_channel, std::array< typename NumericTraits::channel_type, NStopChannels > stop_channels, arg::time_window< typename NumericTraits::abstime_type > time_window, Downstream downstream)
Create a processor that generates ordered pairs of detection events within a time window,...
Definition pair.hpp:513
auto pair_one_between(arg::start_channel< typename NumericTraits::channel_type > start_channel, std::array< typename NumericTraits::channel_type, NStopChannels > stop_channels, arg::time_window< typename NumericTraits::abstime_type > time_window, Downstream downstream)
Create a processor that generates ordered pairs of detection events within a time window,...
Definition pair.hpp:567
auto pair_one(arg::start_channel< typename NumericTraits::channel_type > start_channel, std::array< typename NumericTraits::channel_type, NStopChannels > stop_channels, arg::time_window< typename NumericTraits::abstime_type > time_window, Downstream downstream)
Create a processor that generates ordered pairs of detection events within a time window,...
Definition pair.hpp:461
auto pair_all(arg::start_channel< typename NumericTraits::channel_type > start_channel, std::array< typename NumericTraits::channel_type, NStopChannels > stop_channels, arg::time_window< typename NumericTraits::abstime_type > time_window, Downstream downstream)
Create a processor that generates all ordered pairs of detection events within a time window.
Definition pair.hpp:408
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for start channel parameter.
Definition arg_wrappers.hpp:367
Function argument wrapper for time window parameter.
Definition arg_wrappers.hpp:417
The default numeric traits.
Definition numeric_traits.hpp:27