9#include "arg_wrappers.hpp"
12#include "int_types.hpp"
13#include "introspect.hpp"
14#include "numeric_traits.hpp"
15#include "processor.hpp"
16#include "timing_misc.hpp"
40struct periodic_fit_result {
46class periodic_fitter {
62 std::vector<double> x;
67 explicit periodic_fitter(
u64 length)
68 : n(static_cast<double>(length)), sigma_x((n - 1.0) * n * 0.5),
69 sigma_xx((n - 1.0) * n * (2.0 * n - 1.0) / 6.0),
70 det_XtX(n * sigma_xx - sigma_x * sigma_x), x(length) {
71 std::iota(x.begin(), x.end(), 0.0);
74 [[nodiscard]]
auto fit(std::vector<double>
const &y)
const
75 -> periodic_fit_result {
76 assert(
static_cast<double>(y.size()) == n);
79 double const sigma_y = std::reduce(y.cbegin(), y.cend());
82 double const sigma_xy =
83 std::transform_reduce(x.cbegin(), x.cend(), y.cbegin(), 0.0);
87 double const a = (sigma_xx * sigma_y - sigma_x * sigma_xy) / det_XtX;
88 double const b = (n * sigma_xy - sigma_x * sigma_y) / det_XtX;
92 std::transform_reduce(x.cbegin(), x.cend(), y.cbegin(), 0.0,
93 std::plus<>(), [&](
double x,
double y) {
94 auto const yfit = a + b * x;
95 auto const r = y - yfit;
98 double const mse = ssr / (n - 2.0);
104template <
typename Event,
typename NumericTraits,
typename Downstream>
105 requires processor<Downstream,
106 periodic_sequence_model_event<NumericTraits>>
107class fit_periodic_sequences {
108 using abstime_type = NumericTraits::abstime_type;
114 abstime_type first_tick_time{};
115 abstime_type tick_offset;
117 std::vector<double> relative_ticks;
120 periodic_fitter fitter;
121 double min_interval_cutoff;
122 double max_interval_cutoff;
125 Downstream downstream;
127 LIBTCSPC_NOINLINE
void fit_and_emit(abstime_type last_tick_time) {
128 auto const result = fitter.fit(relative_ticks);
129 if (result.mse > mse_cutoff)
130 throw model_fit_error(
131 "fit periodic sequences: mean squared error exceeded cutoff");
132 if (result.slope < min_interval_cutoff ||
133 result.slope > max_interval_cutoff)
134 throw model_fit_error(
135 "fit periodic sequences: estimated time interval was not in expected range");
141 static_cast<double>(last_tick_time - first_tick_time) -
142 static_cast<double>(tick_offset);
144 downstream.handle(periodic_sequence_model_event<NumericTraits>{
145 last_tick_time,
delay, result.slope});
149 explicit fit_periodic_sequences(arg::length<std::size_t> length,
150 arg::min_interval<double> min_interval,
151 arg::max_interval<double> max_interval,
152 arg::max_mse<double> max_mse,
153 Downstream downstream)
155 tick_offset(static_cast<abstime_type>(max_interval.value) + 10),
156 fitter(length.value), min_interval_cutoff(min_interval.value),
157 max_interval_cutoff(max_interval.value), mse_cutoff(max_mse.value),
158 downstream(std::move(downstream)) {
160 throw std::invalid_argument(
161 "fit_periodic_sequences length must be at least 3");
162 if (min_interval_cutoff > max_interval_cutoff)
163 throw std::invalid_argument(
164 "fit_periodic_sequences min interval cutoff must be less than or equal to max interval cutoff");
165 if (max_interval_cutoff <= 0)
166 throw std::invalid_argument(
167 "fit_periodic_sequences max interval cutoff must be positive");
168 relative_ticks.reserve(len);
171 [[nodiscard]]
auto introspect_node() const -> processor_info {
172 return processor_info(
this,
"fit_periodic_sequences");
175 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
176 return downstream.introspect_graph().push_entry_point(
this);
179 void handle(Event
const &event) {
180 static_assert(std::is_same_v<
decltype(
event.abstime), abstime_type>);
182 if (relative_ticks.empty())
183 first_tick_time =
event.abstime;
185 relative_ticks.push_back(
static_cast<double>(
186 event.abstime - first_tick_time + tick_offset));
188 if (relative_ticks.size() == len) {
189 fit_and_emit(event.abstime);
190 relative_ticks.clear();
195 void handle(Event &&event) { handle(
static_cast<Event
const &
>(event)); }
197 template <
typename OtherEvent>
198 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
199 void handle(OtherEvent &&event) {
200 downstream.handle(std::forward<OtherEvent>(event));
203 void flush() { downstream.flush(); }
272 Downstream downstream) {
273 return internal::fit_periodic_sequences<Event, NumericTraits, Downstream>(
274 length, min_interval, max_interval, max_mse, std::move(downstream));
auto delay(arg::delta< typename NumericTraits::abstime_type > delta, Downstream downstream)
Create a processor that applies an abstime offset to all events.
Definition delay.hpp:123
auto fit_periodic_sequences(arg::length< std::size_t > length, arg::min_interval< double > min_interval, arg::max_interval< double > max_interval, arg::max_mse< double > max_mse, Downstream downstream)
Create a processor that fits fixed-length periodic sequences of events and estimates the start time a...
Definition fit_sequence.hpp:268
std::uint64_t u64
Short name for uint64_t.
Definition int_types.hpp:33
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for length parameter.
Definition arg_wrappers.hpp:197
Function argument wrapper for maximum interval parameter.
Definition arg_wrappers.hpp:257
Function argument wrapper for maximum MSE parameter.
Definition arg_wrappers.hpp:277
Function argument wrapper for minimum interval parameter.
Definition arg_wrappers.hpp:317
The default numeric traits.
Definition numeric_traits.hpp:27