SatCat5
ptp_filters.cc
1 // Copyright 2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/ptp_filters.h>
7 #include <satcat5/ptp_time.h>
8 
9 using satcat5::ptp::boxcar_filter;
10 using satcat5::ptp::median_filter;
30 
31 // Enable additional diagnostics? (0/1/2)
32 static constexpr unsigned DEBUG_VERBOSE = 0;
33 
34 // Maximum unrolled filter size for ptp::MedianFilter.
35 // (Reducing this may help decrease code-size in some cases.)
36 #ifndef SATCAT5_PTP_UNROLL_MEDIAN
37 #define SATCAT5_PTP_UNROLL_MEDIAN 9
38 #endif
39 
40 // Enable psuedorandom dither?
41 #ifndef SATCAT5_PTRK_DITHER
42 #define SATCAT5_PTRK_DITHER 1
43 #endif
44 
45 // Set the default slew rate limit for PI and PII controllers.
46 // (i.e., "10 * SUBNS_PER_MSEC" means max slew of 10 msec/sec.)
47 constexpr s64 SLEW_MAX_IN = s64(10 * SUBNS_PER_MSEC);
48 constexpr u64 SLEW_MAX_OUT = u64(10 * SUBNS_PER_MSEC);
49 
50 // Dither allows averaging over time for sub-LSB resolution.
51 static inline u32 next_dither() {
52  #if SATCAT5_PTRK_DITHER
53  static satcat5::util::Prng prng(0xDEADBEEF);
54  return prng.next();
55  #else
56  return 0;
57  #endif
58 }
59 
60 template <class T = int128_t>
61 static inline T big_dither(unsigned scale) {
62  T dither(next_dither());
63  if (scale > 32) dither <<= (scale - 32);
64  if (scale < 32) dither >>= (32 - scale);
65  return dither;
66 }
67 
68 template <class T>
69 static inline s64 wide_output(const T& x, unsigned scale) {
70  return s64((x + big_dither<T>(scale)) >> scale);
71 }
72 
73 s64 satcat5::ptp::boxcar_filter(const s64* data, unsigned order) {
74  // Passthrough mode?
75  if (order == 0) return data[0];
76  unsigned samps = 1u << order;
77 
78  // Equal-weight sum over the last N samples.
79  // (Avoid sub-LSB bias using pseudorandom dither.)
80  int128_t sum(next_dither() & u32(samps-1));
81  for (unsigned a = 0 ; a < samps ; ++a) {
82  sum += int128_t(data[a]);
83  }
84  return s64(sum >> order);
85 }
86 
87 static inline void sort2(s64& a, s64&b) {
88  if (a > b) {satcat5::util::swap_ref(a, b);}
89 }
90 
91 s64 satcat5::ptp::median_filter(s64* tmp, unsigned samps) {
92  // Passthrough mode?
93  if (samps <= 1) return tmp[0];
94 
95  // Recalculate the median over the last N samples.
96  // Algorithm is a hand-pruned sorting network for each supported size.
97  // See "optmed" method: http://ndevilla.free.fr/median/median/index.html
98  if (SATCAT5_PTP_UNROLL_MEDIAN >= 3 && samps == 3) {
99  sort2(tmp[0], tmp[1]); sort2(tmp[1], tmp[2]);
100  sort2(tmp[0], tmp[1]); return tmp[1];
101  } else if (SATCAT5_PTP_UNROLL_MEDIAN >= 5 && samps == 5) {
102  sort2(tmp[0], tmp[1]); sort2(tmp[3], tmp[4]);
103  sort2(tmp[0], tmp[3]); sort2(tmp[1], tmp[4]);
104  sort2(tmp[1], tmp[2]); sort2(tmp[2], tmp[3]);
105  sort2(tmp[1], tmp[2]); return tmp[2];
106  } else if (SATCAT5_PTP_UNROLL_MEDIAN >= 7 && samps == 7) {
107  sort2(tmp[0], tmp[5]); sort2(tmp[0], tmp[3]);
108  sort2(tmp[1], tmp[6]); sort2(tmp[2], tmp[4]);
109  sort2(tmp[0], tmp[1]); sort2(tmp[3], tmp[5]);
110  sort2(tmp[2], tmp[6]); sort2(tmp[2], tmp[3]);
111  sort2(tmp[3], tmp[6]); sort2(tmp[4], tmp[5]);
112  sort2(tmp[1], tmp[4]); sort2(tmp[1], tmp[3]);
113  sort2(tmp[3], tmp[4]); return tmp[3];
114  } else if (SATCAT5_PTP_UNROLL_MEDIAN >= 9 && samps == 9) {
115  sort2(tmp[1], tmp[2]); sort2(tmp[4], tmp[5]);
116  sort2(tmp[7], tmp[8]); sort2(tmp[0], tmp[1]);
117  sort2(tmp[3], tmp[4]); sort2(tmp[6], tmp[7]);
118  sort2(tmp[1], tmp[2]); sort2(tmp[4], tmp[5]);
119  sort2(tmp[7], tmp[8]); sort2(tmp[0], tmp[3]);
120  sort2(tmp[5], tmp[8]); sort2(tmp[4], tmp[7]);
121  sort2(tmp[3], tmp[6]); sort2(tmp[1], tmp[4]);
122  sort2(tmp[2], tmp[5]); sort2(tmp[4], tmp[7]);
123  sort2(tmp[4], tmp[2]); sort2(tmp[6], tmp[4]);
124  sort2(tmp[4], tmp[2]); return tmp[4];
125  } else {
126  // For windows above the hand-coded limit, use regular sort.
127  satcat5::util::sort(tmp, tmp + samps);
128  return tmp[samps / 2];
129  }
130 }
131 
132 AmplitudeReject::AmplitudeReject(unsigned tau_msec)
133  : m_mean(0)
134  , m_sigma(UINT64_MAX/2)
135  , m_min(SUBNS_PER_NSEC)
136  , m_tau_usec(1000*tau_msec)
137 {
138  // Nothing else to initialize.
139 }
140 
141 void AmplitudeReject::reset() {
142  m_mean = 0;
143  m_sigma = UINT64_MAX/2;
144 }
145 
146 s64 AmplitudeReject::update(s64 next, u32 elapsed_usec) {
147  // Ignore inputs that have already been rejected.
148  if (next == INT64_MAX) return INT64_MAX;
149 
150  // Define various local constants...
151  const int128_t MIN128(m_min);
152  const int128_t MAX128(UINT64_MAX/2);
153  const int128_t SQRTPI2(u64(5382943231ull)); // 2^32 * sqrt(pi/2)
154 
155  // Calculate update rate for the fixed-point IIR filters.
156  // Small-signal approximation for t << tau: k = 2^32 * t / tau
157  elapsed_usec = satcat5::util::min_u32(elapsed_usec, m_tau_usec/2);
158  uint128_t tau(elapsed_usec, 0); // Range 0..2^51
159  tau /= uint128_t(m_tau_usec); // Range 0..2^31
160 
161  // Calculate difference from the mean (may overflow s64).
162  int128_t diff(next); // Range +/- 2^63
163  diff -= int128_t(m_mean); // Range +/- 2^64
164 
165  // IIR filter to estimate the mean.
166  m_mean += s64((diff * tau + big_dither(32)) >> 32u);
167 
168  // Calculate the scaled absolute difference. If the input is normally
169  // distributed, then the expected absolute difference is sigma*sqrt(2/pi).
170  // See also: https://en.wikipedia.org/wiki/Folded_normal_distribution
171  int128_t adiff = (SQRTPI2 * diff.abs() + big_dither(32)) >> 32u;
172  adiff -= int128_t(m_sigma); // Range +/- 2^65
173 
174  // IIR filter to estimate the standard deviation.
175  // (Do not allow sigma to fall below designated minimum.)
176  int128_t sigma(m_sigma); // Range 0..2^63
177  sigma += (adiff * tau + big_dither(32)) >> 32u;
178  if (sigma < MIN128) sigma = MIN128;
179  if (sigma > MAX128) sigma = MAX128;
180  m_sigma = u64(sigma); // Range 0..2^63
181 
182  // Does this sample fall within 6-sigma of the mean?
183  int128_t thresh(m_sigma); thresh *= int128_t(u32(6));
184  return (diff.abs() < thresh) ? next : INT64_MAX;
185 }
186 
188  : m_coeff(coeff)
189  , m_accum(INT128_ZERO)
190  , m_slew(SLEW_MAX_OUT)
191 {
192  set_coeff(coeff); // For error-reporting.
193 }
194 
195 void ControllerPI::set_coeff(const CoeffPI& coeff) {
196  m_coeff = coeff;
197  if (DEBUG_VERBOSE > 0) {
198  auto level = coeff.ok() ? log::DEBUG : log::ERROR;
199  log::Log(level, "ControllerPI: Config")
200  .write10(m_coeff.kp)
201  .write10(m_coeff.ki);
202  } else if (!coeff.ok()) {
203  log::Log(log::ERROR, "ControllerPI: Bad config.");
204  }
205 }
206 
208  m_accum = INT128_ZERO;
209 }
210 
211 void ControllerPI::rate(s64 delta_subns, u32 elapsed_usec) {
212  // Limit input to a sensible range...
213  delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
214  int128_t rate(delta_subns); // Range +/- 2^40
215  rate <<= m_coeff.SCALE; // Range +/- 2^100
216  rate *= int128_t(USEC_PER_SEC); // Range +/- 2^120
217  rate /= int128_t(elapsed_usec); // Range +/- 2^100
218  rate.clamp(int128_t(m_slew) << m_coeff.SCALE);
219  m_accum += rate;
220 }
221 
222 s64 ControllerPI::update(s64 delta_subns, u32 elapsed_usec) {
223  // Ignore invalid inputs and clamp to a sensible limit.
224  if (delta_subns == INT64_MAX) return INT64_MAX;
225  delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
226 
227  // Convert inputs to extra-wide integers for more dynamic range,
228  // then multiply by the KI and KP loop-gain coefficients.
229  int128_t delta_i(delta_subns); // Range +/- 2^36
230  int128_t delta_p(delta_subns); // Range +/- 2^36
231  delta_i *= int128_t(m_coeff.ki); // Range +/- 2^100
232  delta_p *= int128_t(m_coeff.kp); // Range +/- 2^100
233 
234  // Compensate for changes to the effective sample interval T0, using
235  // most recent elapsed time as a proxy for future sample intervals.
236  // * Output to NCO is a rate, held and accumulated for T0 seconds.
237  // Therefore, outputs must be scaled by 1/T0 to compensate.
238  // * I gain is missing implicit T0^2, so net scaling by T0.
239  // * P gain is missing implicit T0, so net scaling is unity.
240  delta_i *= int128_t(elapsed_usec); // Range +/- 2^120
241  delta_p *= int128_t(USEC_PER_SEC); // Range +/- 2^120
242 
243  // Update the accumulator. Calculating sum(KI * phi) instead of
244  // KI * sum(phi) ensures continuity after bandwidth changes.
245  m_accum += delta_i; // Range +/- 2^121
246 
247  // Clamp accumulator term to mitigate windup.
248  int128_t ymax(m_slew); // Range 2^33..2^54
249  m_accum.clamp(ymax << m_coeff.SCALE); // Range +/- 2^114
250 
251  // Tracking output is the sum of all filter terms.
252  // (Sum up to +/- 2^121, output up to +/- 2^61.)
253  int128_t ysum(m_accum + delta_p);
254  ysum.clamp(ymax << m_coeff.SCALE);
255  return wide_output(ysum, m_coeff.SCALE);
256 }
257 
259  : m_coeff(coeff)
260  , m_accum1(INT128_ZERO)
261  , m_accum2(INT256_ZERO)
262  , m_slew(SLEW_MAX_OUT)
263 {
264  set_coeff(coeff); // For error-reporting.
265 }
266 
267 void ControllerPII::set_coeff(const CoeffPII& coeff) {
268  m_coeff = coeff;
269  if (DEBUG_VERBOSE > 0) {
270  auto level = coeff.ok() ? log::DEBUG : log::ERROR;
271  log::Log(level, "ControllerPII: Config")
272  .write10(m_coeff.kp)
273  .write10(m_coeff.ki)
274  .write10(m_coeff.kr);
275  } else if (!coeff.ok()) {
276  log::Log(log::ERROR, "ControllerPII: Bad config.");
277  }
278 }
279 
281  m_accum1 = INT128_ZERO;
282  m_accum2 = INT256_ZERO;
283 }
284 
285 void ControllerPII::rate(s64 delta_subns, u32 elapsed_usec) {
286  // Limit input to a sensible range...
287  delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
288  int256_t rate(delta_subns); // Range +/- 2^40
289  rate <<= m_coeff.SCALE; // Range +/- 2^174
290  rate *= int256_t(USEC_PER_SEC); // Range +/- 2^194
291  rate /= int256_t(elapsed_usec); // Range +/- 2^174
292  rate.clamp(int256_t(SLEW_MAX_OUT) << m_coeff.SCALE);
293  m_accum2 += rate; // Range +/- 2^188
294 }
295 
296 s64 ControllerPII::update(s64 delta_subns, u32 elapsed_usec) {
297  // Ignore invalid inputs and clamp to a sensible limit.
298  if (delta_subns == INT64_MAX) return INT64_MAX;
299  delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
300 
301  // Convert inputs to extra-wide integers for more dynamic range,
302  // then multiply by the KI and KP loop-gain coefficients.
303  int128_t delta_i(delta_subns); // Range +/- 2^36
304  int128_t delta_p(delta_subns); // Range +/- 2^36
305  delta_i *= int128_t(m_coeff.ki); // Range +/- 2^100
306  delta_p *= int128_t(m_coeff.kp); // Range +/- 2^100
307 
308  // Compensate for changes to the effective sample interval T0, using
309  // most recent elapsed time as a proxy for future sample intervals.
310  // * Output to NCO is a rate, held and accumulated for T0 seconds.
311  // Therefore, outputs must be scaled by 1/T0 to compensate.
312  // * J gain is missing implicit T0^3, so net scaling by T0^2.
313  // * I gain is missing implicit T0^2, so net scaling by T0.
314  // * P gain is missing implicit T0, so net scaling is unity.
315  delta_i *= int128_t(elapsed_usec); // Range +/- 2^120
316  delta_p *= int128_t(USEC_PER_SEC); // Range +/- 2^120
317 
318  // Update the primary accumulator, i.e., sum(K2 * phi).
319  // As with ControllerPI, precalculate gain to ensure continuity
320  // and limit the maximum slew-rate to reduce windup.
321  int128_t ymax128(m_slew); // Range 2^33..2^54
322  m_accum1 += delta_i; // Range +/- 2^125
323  m_accum1.clamp(ymax128 << m_coeff.SCALE1); // Range +/- 2^124
324 
325  // Update the secondary accumulator, i.e., sum(sum(K3 * phi)).
326  // To avoid using a third accumulator, re-scale the primary by K3 / K2.
327  int256_t ymax256(m_slew); // Range 2^33..2^54
328  int256_t delta_r(m_accum1); // Range +/- 2^124
329  delta_r *= int256_t(m_coeff.kr); // Range +/- 2^188
330  delta_r *= int256_t(elapsed_usec); // Range +/- 2^208
331  m_accum2 += delta_r; // Range +/- 2^209
332  m_accum2.clamp(ymax256 << m_coeff.SCALE); // Range +/- 2^188
333 
334  // Tracking output is the sum of all filter terms.
335  int128_t ysum((m_accum2 + big_dither(m_coeff.SCALE2)) >> m_coeff.SCALE2);
336  ysum += m_accum1;
337  ysum += delta_p;
338  ysum.clamp(ymax128 << m_coeff.SCALE1);
339  return wide_output(ysum, m_coeff.SCALE1);
340 }
341 
343  const unsigned window, const s64* x, const s64* y)
344 {
345  // Calculate the sum of each input vector.
346  int128_t sum_x = INT128_ZERO, sum_y = INT128_ZERO;
347  for (unsigned n = 0 ; n < window ; ++n) {
348  sum_x += int128_t(x[n]);
349  sum_y += int128_t(y[n]);
350  }
351 
352  // Calculate the covariance terms:
353  // cov_xx = sum(dx * dx) and cov_xy = sum(dx * dy),
354  // where dx[n] = x[n] - mean(x) and dy[n] = y[n] - mean(y).
355  // To avoid loss of precision, don't divide by the window size:
356  // cov_xx * N^2 = sum(dx' * dx'), where dx' = N*x - sum(x).
357  const int128_t win128((u32)window);
358  int256_t cov_xx(INT256_ZERO), cov_xy(INT256_ZERO);
359  for (unsigned n = 0 ; n < window ; ++n) {
360  int256_t dx(int128_t(x[n]) * win128 - sum_x);
361  int256_t dy(int128_t(y[n]) * win128 - sum_y);
362  cov_xx += dx * dx;
363  cov_xy += dx * dy;
364  }
365 
366  // Calculate slope and intercept by linear regression.
367  // https://en.wikipedia.org/wiki/Simple_linear_regression
368  beta = int128_t((cov_xy << TSCALE).div_round(cov_xx));
369  int128_t xbeta((beta * sum_x + big_dither(TSCALE)) >> TSCALE);
370  alpha = int128_t((sum_y - xbeta).div_round(win128));
371 }
372 
374 {
375  return wide_output((alpha << TSCALE) + (beta * int128_t(t)), TSCALE);
376 }
377 
378 ControllerLR_Inner::ControllerLR_Inner(const CoeffLR& coeff, unsigned window)
379  : m_coeff(coeff), m_accum(INT128_ZERO), m_window(window)
380 {
381  set_coeff(coeff); // For error-reporting.
382 }
383 
385 {
386  m_coeff = coeff;
387  if (DEBUG_VERBOSE > 0) {
388  auto level = coeff.ok() ? log::DEBUG : log::ERROR;
389  log::Log(level, "ControllerLR: Config")
390  .write10(m_coeff.ki)
391  .write10(m_coeff.kw);
392  } else if (!coeff.ok()) {
393  log::Log(log::ERROR, "ControllerLR: Bad config.");
394  }
395 }
396 
397 void ControllerLR_Inner::rate(s64 delta_subns, u32 elapsed_usec) {
398  // Limit input to a sensible range...
399  delta_subns = satcat5::util::clamp(delta_subns, SLEW_MAX_IN);
400  int128_t rate(delta_subns);
402  rate *= int128_t(USEC_PER_SEC);
403  rate /= int128_t(elapsed_usec);
404  m_accum += rate;
405 }
406 
407 s64 ControllerLR_Inner::update_inner(const u32* dt, const s64* y) {
408  // Convert incremental timesteps to cumulative time,
409  // using t = 0 for the most recent input sample.
410  // Note: ControllerLR::set_window(...) ensures m_window >= 2.
411  s64 x[m_window]; // NOLINT
412  x[m_window-1] = 0;
413  for (unsigned n = m_window-1 ; n != 0 ; --n) {
414  x[n-1] = x[n] - dt[n];
415  }
416 
417  // Discard degenerate cases where timestamps are too close together.
418  s64 span_usec = -x[0];
419  if (span_usec < 2000) return INT64_MIN;
420 
421  // Calculate slope and intercept by linear regression.
422  LinearRegression fit(m_window, x, y);
423 
424  // Calculate change in slope required for an intercept at t = tau/2.
425  int128_t delta(fit.alpha * int128_t(m_coeff.kw) + fit.beta);
426 
427  // Gradually steer towards the designated target slope.
428  m_accum += delta * int128_t(m_coeff.ki);
429 
430  // Clamp maximum slew rate.
431  m_accum.clamp(int128_t(SLEW_MAX_OUT) << fit.TSCALE);
432  return wide_output(m_accum, fit.TSCALE);
433 }
434 
436  // Reset all inner filter(s).
437  satcat5::ptp::Filter* ptr = m_filters.head();
438  while (ptr) {
439  ptr->reset();
440  ptr = m_filters.next(ptr);
441  }
442  // Reset internal state.
443  m_first = 0;
444  m_rate = 0;
445  m_accum = INT128_ZERO;
446 }
447 
448 void LinearPrediction::rate(s64 delta_subns, u32 elapsed_usec) {
449  // Update all inner filter(s).
450  satcat5::ptp::Filter* ptr = m_filters.head();
451  while (ptr) {
452  ptr->rate(delta_subns, elapsed_usec);
453  ptr = m_filters.next(ptr);
454  }
455  // Update internal state.
456  int128_t rate(delta_subns); // Range +/- 2^63
457  rate *= int128_t(USEC_PER_SEC); // Range +/- 2^83
458  rate /= int128_t(elapsed_usec); // Range +/- 2^63
459  m_rate = s64(rate);
460 }
461 
462 s64 LinearPrediction::update(s64 next, u32 elapsed_usec) {
463  if (m_first) {
464  // First-time initialization?
465  m_accum = int128_t(next) << SCALE;
466  m_first = false;
467  return next;
468  } else {
469  // Increment along estimated trendline.
470  m_accum += incr(elapsed_usec);
471  s64 trend = wide_output(m_accum, SCALE);
472  // Compare actual vs predicted and apply each filter.
473  s64 delta = next - trend;
474  satcat5::ptp::Filter* ptr = m_filters.head();
475  while (ptr) {
476  delta = ptr->update(delta, elapsed_usec);
477  ptr = m_filters.next(ptr);
478  }
479  // Update accumulator state.
480  if (delta != INT64_MIN) m_rate = delta;
481  return trend;
482  }
483 }
484 
485 s64 LinearPrediction::predict(u32 elapsed_usec) const {
486  return wide_output(m_accum + incr(elapsed_usec), SCALE);
487 }
488 
489 int128_t LinearPrediction::incr(u32 elapsed_usec) const {
490  static constexpr u64 TICKS_PER_USEC = satcat5::util::round_u64(
491  satcat5::util::pow2d(SCALE) / double(satcat5::ptp::USEC_PER_SEC));
492  return int128_t(m_rate) * int128_t(TICKS_PER_USEC) * int128_t(elapsed_usec);
493 }
494 
495 s64 RateConversion::convert(s64 offset) const
496 {
497  return wide_output(int128_t(offset) * int128_t(m_scale), SHIFT);
498 }
499 
500 s64 RateConversion::invert(s64 rate) const
501 {
502  int128_t temp(rate); temp <<= SHIFT;
503  return s64(temp.div_round(int128_t(m_scale)));
504 }
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Amplitude-based outlier rejection.
Definition: ptp_filters.h:135
s64 update(s64 next, u32 elapsed_usec) override
Method called for each new input sample.
Definition: ptp_filters.cc:146
Helper class for "ControllerLR" is never used directly.
Definition: ptp_filters.h:427
void set_coeff(const satcat5::ptp::CoeffLR &coeff)
Adjust loop bandwidth.
Definition: ptp_filters.cc:384
ControllerLR_Inner(const satcat5::ptp::CoeffLR &coeff, unsigned window)
Private constructor and destructor.
Definition: ptp_filters.cc:378
void rate(s64 delta, u32 elapsed_usec) override
Partial API from ptp::Filter.
Definition: ptp_filters.cc:397
Loop-filter for a proportional-integral (PI) controller.
Definition: ptp_filters.h:267
void rate(s64 delta, u32 elapsed_usec) override
Required API from ptp::Filter.
Definition: ptp_filters.cc:211
s64 update(s64 next, u32 elapsed_usec) override
Required API from ptp::Filter.
Definition: ptp_filters.cc:222
ControllerPI(const satcat5::ptp::CoeffPI &coeff)
Constructor sets loop bandwidth, which can be changed later.
Definition: ptp_filters.cc:187
void reset() override
Required API from ptp::Filter.
Definition: ptp_filters.cc:207
void set_coeff(const satcat5::ptp::CoeffPI &coeff)
Adjust tracking-loop bandwidth.
Definition: ptp_filters.cc:195
Loop-filter for a proportional-double-integral (PII) controller.
Definition: ptp_filters.h:349
s64 update(s64 next, u32 elapsed_usec) override
Required API from ptp::Filter.
Definition: ptp_filters.cc:296
ControllerPII(const satcat5::ptp::CoeffPII &coeff)
Constructor sets loop bandwidth, which can be changed later.
Definition: ptp_filters.cc:258
void rate(s64 delta, u32 elapsed_usec) override
Required API from ptp::Filter.
Definition: ptp_filters.cc:285
void reset() override
Required API from ptp::Filter.
Definition: ptp_filters.cc:280
void set_coeff(const satcat5::ptp::CoeffPII &coeff)
Adjust tracking-loop bandwidth.
Definition: ptp_filters.cc:267
Define the basic chain-of-filters API.
Definition: ptp_filters.h:38
virtual void rate(s64 delta_subns, u32 elapsed_usec)
Optional handler for fast-acquisition; override if required.
Definition: ptp_filters.h:48
virtual void reset()=0
Flush previous inputs and reset to a neutral state.
virtual s64 update(s64 next, u32 elapsed_usec)=0
Method called for each new input sample.
An inline filter that iteratively estimates linear trends.
Definition: ptp_filters.h:505
s64 predict(u32 elapsed_usec) const
Extrapolate trendline relative to most recent update() event.
Definition: ptp_filters.cc:485
void rate(s64 delta, u32 elapsed_usec) override
Required API from ptp::Filter.
Definition: ptp_filters.cc:448
s64 update(s64 next, u32 elapsed_usec) override
Required API from ptp::Filter.
Definition: ptp_filters.cc:462
void reset() override
Required API from ptp::Filter.
Definition: ptp_filters.cc:435
Convert normalized frequency offset to ticks-per-clock.
Definition: ptp_filters.h:545
s64 invert(s64 rate) const
Inverse conversion (ticks-per-clock ==> normalized rate)
Definition: ptp_filters.cc:500
s64 convert(s64 offset) const
Forward conversion (normalized rate ==> ticks-per-clock)
Definition: ptp_filters.cc:495
T * next(const T *item) const
Fetch pointer to the next item.
Definition: list.h:261
Simple cross-platform psuedorandom number generator (PRNG).
Definition: utils.h:370
u32 next()
Range [0..2^32)
Definition: utils.cc:194
Chainable filters for use with ptp::TrackingController.
High-precision "Time" object for use with PTP / IEEE1588.
constexpr s64 SUBNS_PER_MSEC
Define commonly used scaling factors.
Definition: ptp_time.h:30
constexpr s64 SUBNS_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:31
constexpr s64 USEC_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:26
constexpr s64 SUBNS_PER_NSEC
Define commonly used scaling factors.
Definition: ptp_time.h:28
Loop-filter coefficients for use with the "ControllerLR" class.
Definition: ptp_filters.h:400
bool ok() const
Are all coefficients large enough to mitigate rounding error?
Definition: ptp_filters.h:411
Loop-filter coefficients for use with the "ControllerPI" class.
Definition: ptp_filters.h:224
bool ok() const
Are all coefficients large enough to mitigate rounding error?
Definition: ptp_filters.h:235
static constexpr unsigned SCALE
Fixed-point scaling of each coefficient by 2^-N.
Definition: ptp_filters.h:239
Loop-filter coefficients for use with the "ControllerPII" class.
Definition: ptp_filters.h:295
static constexpr unsigned SCALE2
Fixed-point scaling of each coefficient by 2^-N.
Definition: ptp_filters.h:313
static constexpr unsigned SCALE
Fixed-point scaling of each coefficient by 2^-N.
Definition: ptp_filters.h:314
bool ok() const
Are all coefficients large enough to mitigate rounding error?
Definition: ptp_filters.h:307
static constexpr unsigned SCALE1
Fixed-point scaling of each coefficient by 2^-N.
Definition: ptp_filters.h:312
Stateless linear regression calculator.
Definition: ptp_filters.h:376
s64 extrapolate(s64 x) const
Extrapolate relative to the most recent sample.
Definition: ptp_filters.cc:373
satcat5::util::int128_t beta
Parameters for the best-fit line.
Definition: ptp_filters.h:381
constexpr LinearRegression()
Placeholder constructor.
Definition: ptp_filters.h:385
satcat5::util::int128_t alpha
Parameters for the best-fit line.
Definition: ptp_filters.h:380
static constexpr unsigned TSCALE
Parameters for the best-fit line.
Definition: ptp_filters.h:379
void clamp(const WideSigned< W > &limit_pos)
Clamp input to +/- limit_pos.
Definition: wide_integer.h:370
void swap_ref(T &x, T &y)
Swap two values using a temporary variable.
Definition: utils.h:350
satcat5::util::Prng prng
Global instance of the Prng class.
Definition: utils.cc:20
void sort(T *begin, T *end)
Templated in-place stable sort for small arrays.
Definition: utils.h:357
constexpr satcat5::util::int256_t INT256_ZERO(u32(0))
Shorthand for commonly used constants.
satcat5::util::WideSigned< 4 > int128_t
Shorthand for commonly used sizes.
Definition: wide_integer.h:604
constexpr satcat5::util::int128_t INT128_ZERO(u32(0))
Shorthand for commonly used constants.