libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
binning.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 "context.hpp"
12#include "histogram_events.hpp"
13#include "int_arith.hpp"
14#include "int_types.hpp"
15#include "introspect.hpp"
16#include "numeric_traits.hpp"
17#include "processor.hpp"
18
19#include <algorithm>
20#include <cassert>
21#include <concepts>
22#include <cstddef>
23#include <functional>
24#include <iterator>
25#include <limits>
26#include <optional>
27#include <span>
28#include <stdexcept>
29#include <type_traits>
30#include <utility>
31#include <vector>
32
33namespace tcspc {
34
44template <typename T, typename Event, typename Datapoint>
46 std::move_constructible<T> && requires(T const &m, Event const &e) {
47 { m(e) } -> std::same_as<Datapoint>;
48 };
49
66template <typename T, typename Datapoint, typename BinIndex>
68 std::move_constructible<T> && requires(T &m, Datapoint d) {
69 { m(d) } -> std::same_as<std::optional<BinIndex>>;
70 };
71
72namespace internal {
73
74template <typename Event, typename NumericTraits, typename DataMapper,
75 typename Downstream>
76 requires data_mapper_for<DataMapper, Event,
77 typename NumericTraits::datapoint_type> &&
80 DataMapper mapper;
81
82 Downstream downstream;
83
84 public:
85 explicit map_to_datapoints(DataMapper mapper, Downstream downstream)
86 : mapper(std::move(mapper)), downstream(std::move(downstream)) {}
87
88 [[nodiscard]] auto introspect_node() const -> processor_info {
89 return processor_info(this, "map_to_datapoints");
90 }
91
92 [[nodiscard]] auto introspect_graph() const -> processor_graph {
93 return downstream.introspect_graph().push_entry_point(this);
94 }
95
96 void handle(Event const &event) {
97 downstream.handle(
98 datapoint_event<NumericTraits>{std::invoke(mapper, event)});
99 }
100
101 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
102 void handle(Event &&event) { handle(static_cast<Event const &>(event)); }
103
104 template <typename OtherEvent>
105 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
106 void handle(OtherEvent &&event) {
107 downstream.handle(std::forward<OtherEvent>(event));
108 }
109
110 void flush() { downstream.flush(); }
111};
112
113} // namespace internal
114
146template <typename Event, typename NumericTraits = default_numeric_traits,
147 typename DataMapper, typename Downstream>
148auto map_to_datapoints(DataMapper mapper, Downstream downstream) {
149 return internal::map_to_datapoints<Event, NumericTraits, DataMapper,
150 Downstream>(std::move(mapper),
151 std::move(downstream));
152}
153
163template <typename NumericTraits = default_numeric_traits>
165 public:
167 template <typename Event>
168 auto operator()(Event const &event) const
169 -> NumericTraits::datapoint_type {
170 static_assert(
171 internal::representable_in<decltype(event.difftime),
172 typename NumericTraits::datapoint_type>,
173 "difftime_data_mapper does not allow narrowing conversion");
174 return event.difftime;
175 }
176};
177
187template <typename NumericTraits = default_numeric_traits>
189 public:
191 template <typename Event>
192 auto operator()(Event const &event) const
193 -> NumericTraits::datapoint_type {
194 static_assert(
195 internal::representable_in<decltype(event.count),
196 typename NumericTraits::datapoint_type>,
197 "count_data_mapper does not allow narrowing conversion");
198 return event.count;
199 }
200};
201
211template <typename NumericTraits = default_numeric_traits>
213 public:
215 template <typename Event>
216 auto operator()(Event const &event) const
217 -> NumericTraits::datapoint_type {
218 static_assert(
219 internal::representable_in<decltype(event.channel),
220 typename NumericTraits::datapoint_type>,
221 "channel_data_mapper does not allow narrowing conversion");
222 return event.channel;
223 }
224};
225
226namespace internal {
227
228template <typename NumericTraits, typename BinMapper, typename Downstream>
229 requires bin_mapper_for<BinMapper, typename NumericTraits::datapoint_type,
230 typename NumericTraits::bin_index_type> &&
231 processor<Downstream, bin_increment_event<NumericTraits>>
232class map_to_bins {
233 static_assert(with_datapoint_type<NumericTraits>);
234 static_assert(with_bin_index_type<NumericTraits>);
235
236 BinMapper bin_mapper;
237 Downstream downstream;
238
239 public:
240 explicit map_to_bins(BinMapper bin_mapper, Downstream downstream)
241 : bin_mapper(std::move(bin_mapper)),
242 downstream(std::move(downstream)) {}
243
244 [[nodiscard]] auto introspect_node() const -> processor_info {
245 return processor_info(this, "map_to_bins");
246 }
247
248 [[nodiscard]] auto introspect_graph() const -> processor_graph {
249 return downstream.introspect_graph().push_entry_point(this);
250 }
251
252 template <typename NT> void handle(datapoint_event<NT> const &event) {
253 static_assert(std::is_same_v<typename NT::datapoint_type,
254 typename NumericTraits::datapoint_type>);
255 auto bin = std::invoke(bin_mapper, event.value);
256 if (bin)
257 downstream.handle(bin_increment_event<NumericTraits>{bin.value()});
258 }
259
260 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
261 template <typename NT> void handle(datapoint_event<NT> &&event) {
262 handle(static_cast<datapoint_event<NT> const &>(event));
263 }
264
265 template <typename OtherEvent>
266 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
267 void handle(OtherEvent &&event) {
268 downstream.handle(std::forward<OtherEvent>(event));
269 }
270
271 void flush() { downstream.flush(); }
272};
273
274} // namespace internal
275
306template <typename NumericTraits = default_numeric_traits, typename BinMapper,
307 typename Downstream>
308auto map_to_bins(BinMapper bin_mapper, Downstream downstream) {
309 return internal::map_to_bins<NumericTraits, BinMapper, Downstream>(
310 std::move(bin_mapper), std::move(downstream));
311}
312
338template <unsigned NDataBits, unsigned NHistoBits, bool Flip = false,
339 typename NumericTraits = default_numeric_traits>
343 static_assert(NHistoBits <= 64); // Assumption made below.
344 static_assert(NDataBits >= NHistoBits);
345 static_assert(NDataBits <=
346 8 * sizeof(typename NumericTraits::datapoint_type));
347 static_assert(NHistoBits <=
348 8 * sizeof(typename NumericTraits::bin_index_type));
349 // Bin indices are always non-negative.
350 static_assert(std::is_unsigned_v<typename NumericTraits::bin_index_type> ||
351 NHistoBits <
352 8 * sizeof(typename NumericTraits::bin_index_type));
353
355 [[nodiscard]] auto n_bins() const -> std::size_t {
356 return std::size_t{1} << NHistoBits;
357 }
358
360 auto operator()(NumericTraits::datapoint_type datapoint) const
361 -> std::optional<typename NumericTraits::bin_index_type> {
362 using datapoint_type = NumericTraits::datapoint_type;
363 using bin_index_type = NumericTraits::bin_index_type;
364
365 static constexpr int shift = NDataBits - NHistoBits;
366 static_assert(shift <= 8 * sizeof(datapoint_type)); // Ensured above.
367 if constexpr (shift == 8 * sizeof(datapoint_type))
368 return bin_index_type{0}; // Shift would be UB.
369
370 // Convert to unsigned (if necessary) _after_ the shift so that
371 // negative datapoints are mapped to indices above max.
372 auto const shifted = internal::ensure_unsigned(datapoint >> shift);
373 static constexpr auto max_bin_index = static_cast<bin_index_type>(
374 NHistoBits == 64 ? std::numeric_limits<u64>::max()
375 : (u64(1) << NHistoBits) - 1);
376 if (shifted > max_bin_index)
377 return std::nullopt;
378
379 auto const bin_index = static_cast<bin_index_type>(shifted);
380 if constexpr (Flip)
381 return static_cast<bin_index_type>(max_bin_index - bin_index);
382 else
383 return bin_index;
384 }
385};
386
395template <typename NumericTraits = default_numeric_traits>
397 NumericTraits::datapoint_type off;
398 NumericTraits::datapoint_type bwidth;
399 NumericTraits::bin_index_type max_index;
400 bool clamp;
401
404
405 // Assumptions used by implementation.
406 static_assert(sizeof(typename NumericTraits::datapoint_type) <=
407 sizeof(u64));
408 static_assert(sizeof(typename NumericTraits::bin_index_type) <=
409 sizeof(u64));
410
411 public:
434 max_bin_index,
435 arg::clamp<bool> clamp = arg::clamp{false})
436 : off(offset.value), bwidth(bin_width.value),
437 max_index(max_bin_index.value), clamp(clamp.value) {
438 if (bwidth == 0)
439 throw std::invalid_argument(
440 "linear_bin_mapper bin_width must not be zero");
441 if (max_index < 0)
442 throw std::invalid_argument(
443 "linear_bin_mapper max_bin_index must not be negative");
444 }
445
447 [[nodiscard]] auto n_bins() const -> std::size_t {
448 return std::size_t(max_index) + 1;
449 }
450
452 auto operator()(NumericTraits::datapoint_type datapoint) const
453 -> std::optional<typename NumericTraits::bin_index_type> {
454 using bin_index_type = NumericTraits::bin_index_type;
455
456 if (bwidth < 0 ? datapoint > off : datapoint < off)
457 return clamp ? std::make_optional(bin_index_type{0})
458 : std::nullopt;
459 // Note we always divide non-negative by positive or non-positive by
460 // negative, to avoid being affected by truncation toward zero.
461 auto const scaled = (datapoint - off) / bwidth;
462 assert(scaled >= 0);
463 if (u64(scaled) > u64(max_index))
464 return clamp ? std::make_optional(max_index) : std::nullopt;
465 return static_cast<bin_index_type>(scaled);
466 }
467};
468
474template <typename T> class unique_bin_mapper_accessor {
475 std::function<std::vector<T>()> values_fn;
476
477 public:
479 template <typename Func>
480 explicit unique_bin_mapper_accessor(Func values_func)
481 : values_fn(values_func) {}
482
486 auto values() -> std::vector<T> { return values_fn(); }
487};
488
506template <typename NumericTraits = default_numeric_traits>
508 using datapoint_type = NumericTraits::datapoint_type;
509 using bin_index_type = NumericTraits::bin_index_type;
510 bin_index_type max_index;
511 std::vector<datapoint_type> values;
513
514 public:
525 &&tracker,
527 max_bin_index)
528 : max_index(max_bin_index.value), trk(std::move(tracker)) {
529 if (max_index < 0)
530 throw std::invalid_argument(
531 "unique_bin_mapper max_bin_index must not be negative");
532 trk.register_accessor_factory([](auto &tracker) {
533 auto *self =
536 [self] { return self->values; });
537 });
538 }
539
541 [[nodiscard]] auto n_bins() const -> std::size_t {
542 return std::size_t(max_index) + 1;
543 }
544
546 auto operator()(NumericTraits::datapoint_type datapoint)
547 -> std::optional<typename NumericTraits::bin_index_type> {
548 auto it = std::find(values.begin(), values.end(), datapoint);
549 if (it == values.end()) {
550 values.push_back(datapoint);
551 it = std::prev(values.end());
552 }
553 auto idx = std::distance(values.begin(), it);
554 return u64(idx) <= u64(max_index)
555 ? std::make_optional(bin_index_type(idx))
556 : std::nullopt;
557 }
558};
559
560namespace internal {
561
562template <typename StartEvent, typename StopEvent, typename NumericTraits,
563 typename Downstream>
565 static_assert(
566 processor<Downstream, bin_increment_cluster_event<NumericTraits>>);
567
568 bool in_cluster = false;
569 std::vector<typename NumericTraits::bin_index_type> cur_cluster;
570
571 Downstream downstream;
572
573 public:
574 explicit cluster_bin_increments(Downstream downstream)
575 : downstream(std::move(downstream)) {}
576
577 [[nodiscard]] auto introspect_node() const -> processor_info {
578 return processor_info(this, "cluster_bin_increments");
579 }
580
581 [[nodiscard]] auto introspect_graph() const -> processor_graph {
582 return downstream.introspect_graph().push_entry_point(this);
583 }
584
585 template <typename NT> void handle(bin_increment_event<NT> const &event) {
586 static_assert(std::is_same_v<typename NT::bin_index_type,
587 typename NumericTraits::bin_index_type>);
588 if (in_cluster)
589 cur_cluster.push_back(event.bin_index);
590 }
591
592 void handle(StartEvent const & /* event */) {
593 cur_cluster.clear();
594 in_cluster = true;
595 }
596
597 void handle(StopEvent const & /* event */) {
598 if (in_cluster) {
599 auto const e = bin_increment_cluster_event<NumericTraits>{
600 ad_hoc_bucket(std::span(cur_cluster))};
601 downstream.handle(e);
602 in_cluster = false;
603 }
604 }
605
606 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
607 template <typename NT> void handle(bin_increment_event<NT> &&event) {
608 handle(static_cast<bin_increment_event<NT> const &>(event));
609 }
610
611 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
612 void handle(StartEvent &&event) {
613 handle(static_cast<StartEvent const &>(event));
614 }
615
616 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
617 void handle(StopEvent &&event) {
618 handle(static_cast<StopEvent const &>(event));
619 }
620
621 template <typename OtherEvent>
622 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
623 void handle(OtherEvent &&event) {
624 downstream.handle(std::forward<OtherEvent>(event));
625 }
626
627 void flush() { downstream.flush(); }
628};
629
630} // namespace internal
631
658template <typename StartEvent, typename StopEvent,
659 typename NumericTraits = default_numeric_traits, typename Downstream>
660auto cluster_bin_increments(Downstream downstream) {
661 return internal::cluster_bin_increments<StartEvent, StopEvent,
662 NumericTraits, Downstream>(
663 std::move(downstream));
664}
665
666} // namespace tcspc
Tracker that mediates access to objects via a tcspc::context.
Definition context.hpp:39
Data mapper mapping channel to the data value.
Definition binning.hpp:212
auto operator()(Event const &event) const -> NumericTraits::datapoint_type
Implements data mapper requirement.
Definition binning.hpp:216
Data mapper mapping count to the data value.
Definition binning.hpp:188
auto operator()(Event const &event) const -> NumericTraits::datapoint_type
Implements data mapper requirement.
Definition binning.hpp:192
Data mapper mapping difference time to the data value.
Definition binning.hpp:164
auto operator()(Event const &event) const -> NumericTraits::datapoint_type
Implements data mapper requirement.
Definition binning.hpp:168
linear_bin_mapper(arg::offset< typename NumericTraits::datapoint_type > offset, arg::bin_width< typename NumericTraits::datapoint_type > bin_width, arg::max_bin_index< typename NumericTraits::bin_index_type > max_bin_index, arg::clamp< bool > clamp=arg::clamp{false})
Construct with parameters.
Definition binning.hpp:430
auto operator()(NumericTraits::datapoint_type datapoint) const -> std::optional< typename NumericTraits::bin_index_type >
Implements bin mapper requirement.
Definition binning.hpp:452
auto n_bins() const -> std::size_t
Implements bin mapper requirement.
Definition binning.hpp:447
Accessor for tcspc::unique_bin_mapper data.
Definition binning.hpp:474
auto values() -> std::vector< T >
Return the datapoint values assigned to bin indices.
Definition binning.hpp:486
auto operator()(NumericTraits::datapoint_type datapoint) -> std::optional< typename NumericTraits::bin_index_type >
Implements bin mapper requirement.
Definition binning.hpp:546
auto n_bins() const -> std::size_t
Implements bin mapper requirement.
Definition binning.hpp:541
unique_bin_mapper(access_tracker< unique_bin_mapper_accessor< typename NumericTraits::datapoint_type > > &&tracker, arg::max_bin_index< typename NumericTraits::bin_index_type > max_bin_index)
Construct with context and parameter.
Definition binning.hpp:522
Concept that is satisfied when T conforms to the libtcspc bin mapper interface mapping datapoints of ...
Definition binning.hpp:67
Concept that is satisfied when T conforms to the libtcspc data mapper interface for event type Event ...
Definition binning.hpp:45
Concept that is satisfied when a processor handles the given event types and flush.
Definition processor.hpp:119
Concept that is satisfied when NT provides an integral bin_index_type.
Definition numeric_traits.hpp:123
Concept that is satisfied when NT provides an integral datapoint_type.
Definition numeric_traits.hpp:114
auto ad_hoc_bucket(std::span< T > s) -> bucket< T >
Create a tcspc::bucket referencing a span.
Definition bucket.hpp:488
#define LIBTCSPC_OBJECT_FROM_TRACKER(obj_type, tracker_field_name, tracker)
Recover the object address from a tcspc::access_tracker embedded in the object.
Definition context.hpp:255
auto map_to_datapoints(DataMapper mapper, Downstream downstream)
Create a processor that maps arbitrary time-tagged events to datapoint events.
Definition binning.hpp:148
auto map_to_bins(BinMapper bin_mapper, Downstream downstream)
Create a processor that maps datapoints to histogram bin indices.
Definition binning.hpp:308
auto cluster_bin_increments(Downstream downstream)
Create a processor collecting binned data into clusters.
Definition binning.hpp:660
std::uint64_t u64
Short name for uint64_t.
Definition int_types.hpp:33
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for bin width parameter.
Definition arg_wrappers.hpp:57
Function argument wrapper for clamp parameter.
Definition arg_wrappers.hpp:87
Function argument wrapper for maximum bin index parameter.
Definition arg_wrappers.hpp:217
Function argument wrapper for offset parameter.
Definition arg_wrappers.hpp:347
The default numeric traits.
Definition numeric_traits.hpp:27
Bin mapper that discards the least significant bits.
Definition binning.hpp:340
auto operator()(NumericTraits::datapoint_type datapoint) const -> std::optional< typename NumericTraits::bin_index_type >
Implements bin mapper requirement.
Definition binning.hpp:360
auto n_bins() const -> std::size_t
Implements bin mapper requirement.
Definition binning.hpp:355