9#include "arg_wrappers.hpp"
11#include "data_types.hpp"
13#include "int_types.hpp"
14#include "introspect.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 DataTypes,
typename Downstream>
105class fit_periodic_sequences {
107 processor<Downstream, periodic_sequence_model_event<DataTypes>>);
109 using abstime_type =
typename DataTypes::abstime_type;
115 abstime_type first_tick_time{};
116 abstime_type tick_offset;
118 std::vector<double> relative_ticks;
121 periodic_fitter fitter;
122 double min_interval_cutoff;
123 double max_interval_cutoff;
126 Downstream downstream;
128 LIBTCSPC_NOINLINE
void fit_and_emit(abstime_type last_tick_time) {
129 auto const result = fitter.fit(relative_ticks);
130 if (result.mse > mse_cutoff)
131 throw model_fit_error(
132 "fit periodic sequences: mean squared error exceeded cutoff");
133 if (result.slope < min_interval_cutoff ||
134 result.slope > max_interval_cutoff)
135 throw model_fit_error(
136 "fit periodic sequences: estimated time interval was not in expected range");
142 static_cast<double>(last_tick_time - first_tick_time) -
143 static_cast<double>(tick_offset);
145 downstream.handle(periodic_sequence_model_event<DataTypes>{
146 last_tick_time,
delay, result.slope});
150 explicit fit_periodic_sequences(arg::length<std::size_t> length,
151 arg::min_interval<double> min_interval,
152 arg::max_interval<double> max_interval,
153 arg::max_mse<double> max_mse,
154 Downstream downstream)
156 tick_offset(static_cast<abstime_type>(max_interval.value) + 10),
157 fitter(length.value), min_interval_cutoff(min_interval.value),
158 max_interval_cutoff(max_interval.value), mse_cutoff(max_mse.value),
159 downstream(std::move(downstream)) {
161 throw std::invalid_argument(
162 "fit_periodic_sequences length must be at least 3");
163 if (min_interval_cutoff > max_interval_cutoff)
164 throw std::invalid_argument(
165 "fit_periodic_sequences min interval cutoff must be less than or equal to max interval cutoff");
166 if (max_interval_cutoff <= 0)
167 throw std::invalid_argument(
168 "fit_periodic_sequences max interval cutoff must be positive");
169 relative_ticks.reserve(len);
172 [[nodiscard]]
auto introspect_node() const -> processor_info {
173 return processor_info(
this,
"fit_periodic_sequences");
176 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
177 return downstream.introspect_graph().push_entry_point(
this);
180 void handle(Event
const &event) {
181 static_assert(std::is_same_v<
decltype(
event.abstime), abstime_type>);
183 if (relative_ticks.empty())
184 first_tick_time =
event.abstime;
186 relative_ticks.push_back(
static_cast<double>(
187 event.abstime - first_tick_time + tick_offset));
189 if (relative_ticks.size() == len) {
190 fit_and_emit(event.abstime);
191 relative_ticks.clear();
196 void handle(Event &&event) { handle(
static_cast<Event
const &
>(event)); }
198 template <
typename OtherEvent>
199 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
200 void handle(OtherEvent &&event) {
201 downstream.handle(std::forward<OtherEvent>(event));
204 void flush() { downstream.flush(); }
273 Downstream downstream) {
274 return internal::fit_periodic_sequences<Event, DataTypes, Downstream>(
275 length, min_interval, max_interval, max_mse, std::move(downstream));
auto delay(arg::delta< typename DataTypes::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:269
std::uint64_t u64
Short name for uint64_t.
Definition int_types.hpp:33
libtcspc namespace.
Definition acquire.hpp:29
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 data type set.
Definition data_types.hpp:24