Blame linreg.c

Packit 9c3e7e
/**
Packit 9c3e7e
 * @file linreg.c
Packit 9c3e7e
 * @brief Implements an adaptive servo based on linear regression.
Packit 9c3e7e
 * @note Copyright (C) 2014 Miroslav Lichvar <mlichvar@redhat.com>
Packit 9c3e7e
 *
Packit 9c3e7e
 * This program is free software; you can redistribute it and/or modify
Packit 9c3e7e
 * it under the terms of the GNU General Public License as published by
Packit 9c3e7e
 * the Free Software Foundation; either version 2 of the License, or
Packit 9c3e7e
 * (at your option) any later version.
Packit 9c3e7e
 *
Packit 9c3e7e
 * This program is distributed in the hope that it will be useful,
Packit 9c3e7e
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 9c3e7e
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 9c3e7e
 * GNU General Public License for more details.
Packit 9c3e7e
 *
Packit 9c3e7e
 * You should have received a copy of the GNU General Public License along
Packit 9c3e7e
 * with this program; if not, write to the Free Software Foundation, Inc.,
Packit 9c3e7e
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 9c3e7e
 */
Packit 9c3e7e
#include <stdlib.h>
Packit 9c3e7e
#include <math.h>
Packit 9c3e7e
Packit 9c3e7e
#include "linreg.h"
Packit 9c3e7e
#include "print.h"
Packit 9c3e7e
#include "servo_private.h"
Packit 9c3e7e
Packit 9c3e7e
/* Maximum and minimum number of points used in regression,
Packit 9c3e7e
   defined as a power of 2 */
Packit 9c3e7e
#define MAX_SIZE 6
Packit 9c3e7e
#define MIN_SIZE 2
Packit 9c3e7e
Packit 9c3e7e
#define MAX_POINTS (1 << MAX_SIZE)
Packit 9c3e7e
Packit 9c3e7e
/* Smoothing factor used for long-term prediction error */
Packit 9c3e7e
#define ERR_SMOOTH 0.02
Packit 9c3e7e
/* Number of updates used for initialization */
Packit 9c3e7e
#define ERR_INITIAL_UPDATES 10
Packit 9c3e7e
/* Maximum ratio of two err values to be considered equal */
Packit 9c3e7e
#define ERR_EQUALS 1.05
Packit 9c3e7e
Packit 9c3e7e
/* Uncorrected local time vs remote time */
Packit 9c3e7e
struct point {
Packit 9c3e7e
	uint64_t x;
Packit 9c3e7e
	uint64_t y;
Packit 9c3e7e
	double w;
Packit 9c3e7e
};
Packit 9c3e7e
Packit 9c3e7e
struct result {
Packit 9c3e7e
	/* Slope and intercept from latest regression */
Packit 9c3e7e
	double slope;
Packit 9c3e7e
	double intercept;
Packit 9c3e7e
	/* Exponential moving average of prediction error */
Packit 9c3e7e
	double err;
Packit 9c3e7e
	/* Number of initial err updates */
Packit 9c3e7e
	int err_updates;
Packit 9c3e7e
};
Packit 9c3e7e
Packit 9c3e7e
struct linreg_servo {
Packit 9c3e7e
	struct servo servo;
Packit 9c3e7e
	/* Circular buffer of points */
Packit 9c3e7e
	struct point points[MAX_POINTS];
Packit 9c3e7e
	/* Current time in x, y */
Packit 9c3e7e
	struct point reference;
Packit 9c3e7e
	/* Number of stored points */
Packit 9c3e7e
	unsigned int num_points;
Packit 9c3e7e
	/* Index of the newest point */
Packit 9c3e7e
	unsigned int last_point;
Packit 9c3e7e
	/* Remainder from last update of reference.x */
Packit 9c3e7e
	double x_remainder;
Packit 9c3e7e
	/* Local time stamp of last update */
Packit 9c3e7e
	uint64_t last_update;
Packit 9c3e7e
	/* Regression results for all sizes */
Packit 9c3e7e
	struct result results[MAX_SIZE - MIN_SIZE + 1];
Packit 9c3e7e
	/* Selected size */
Packit 9c3e7e
	unsigned int size;
Packit 9c3e7e
	/* Current frequency offset of the clock */
Packit 9c3e7e
	double clock_freq;
Packit 9c3e7e
	/* Expected interval between updates */
Packit 9c3e7e
	double update_interval;
Packit 9c3e7e
	/* Current ratio between remote and local frequency */
Packit 9c3e7e
	double frequency_ratio;
Packit 9c3e7e
	/* Upcoming leap second */
Packit 9c3e7e
	int leap;
Packit 9c3e7e
};
Packit 9c3e7e
Packit 9c3e7e
static void linreg_destroy(struct servo *servo)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s = container_of(servo, struct linreg_servo, servo);
Packit 9c3e7e
	free(s);
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void move_reference(struct linreg_servo *s, int64_t x, int64_t y)
Packit 9c3e7e
{
Packit 9c3e7e
	struct result *res;
Packit 9c3e7e
	unsigned int i;
Packit 9c3e7e
Packit 9c3e7e
	s->reference.x += x;
Packit 9c3e7e
	s->reference.y += y;
Packit 9c3e7e
Packit 9c3e7e
	/* Update intercepts for new reference */
Packit 9c3e7e
	for (i = MIN_SIZE; i <= MAX_SIZE; i++) {
Packit 9c3e7e
		res = &s->results[i - MIN_SIZE];
Packit 9c3e7e
		res->intercept += x * res->slope - y;
Packit 9c3e7e
	}
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void update_reference(struct linreg_servo *s, uint64_t local_ts)
Packit 9c3e7e
{
Packit 9c3e7e
	double x_interval;
Packit 9c3e7e
	int64_t y_interval;
Packit 9c3e7e
Packit 9c3e7e
	if (s->last_update) {
Packit 9c3e7e
		y_interval = local_ts - s->last_update;
Packit 9c3e7e
Packit 9c3e7e
		/* Remove current frequency correction from the interval */
Packit 9c3e7e
		x_interval = y_interval / (1.0 + s->clock_freq / 1e9);
Packit 9c3e7e
		x_interval += s->x_remainder;
Packit 9c3e7e
		s->x_remainder = x_interval - (int64_t)x_interval;
Packit 9c3e7e
Packit 9c3e7e
		move_reference(s, (int64_t)x_interval, y_interval);
Packit 9c3e7e
	}
Packit 9c3e7e
Packit 9c3e7e
	s->last_update = local_ts;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void add_sample(struct linreg_servo *s, int64_t offset, double weight)
Packit 9c3e7e
{
Packit 9c3e7e
	s->last_point = (s->last_point + 1) % MAX_POINTS;
Packit 9c3e7e
Packit 9c3e7e
	s->points[s->last_point].x = s->reference.x;
Packit 9c3e7e
	s->points[s->last_point].y = s->reference.y - offset;
Packit 9c3e7e
	s->points[s->last_point].w = weight;
Packit 9c3e7e
Packit 9c3e7e
	if (s->num_points < MAX_POINTS)
Packit 9c3e7e
		s->num_points++;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void regress(struct linreg_servo *s)
Packit 9c3e7e
{
Packit 9c3e7e
	double x, y, y0, e, x_sum, y_sum, xy_sum, x2_sum, w, w_sum;
Packit 9c3e7e
	unsigned int i, l, n, size;
Packit 9c3e7e
	struct result *res;
Packit 9c3e7e
Packit 9c3e7e
	x_sum = 0.0, y_sum = 0.0, xy_sum = 0.0, x2_sum = 0.0; w_sum = 0.0;
Packit 9c3e7e
	i = 0;
Packit 9c3e7e
Packit 9c3e7e
	y0 = (int64_t)(s->points[s->last_point].y - s->reference.y);
Packit 9c3e7e
Packit 9c3e7e
	for (size = MIN_SIZE; size <= MAX_SIZE; size++) {
Packit 9c3e7e
		n = 1 << size;
Packit 9c3e7e
		if (n > s->num_points)
Packit 9c3e7e
			/* Not enough points for this size */
Packit 9c3e7e
			break;
Packit 9c3e7e
Packit 9c3e7e
		res = &s->results[size - MIN_SIZE];
Packit 9c3e7e
Packit 9c3e7e
		/* Update moving average of the prediction error */
Packit 9c3e7e
		if (res->slope) {
Packit 9c3e7e
			e = fabs(res->intercept - y0);
Packit 9c3e7e
			if (res->err_updates < ERR_INITIAL_UPDATES) {
Packit 9c3e7e
				res->err *= res->err_updates;
Packit 9c3e7e
				res->err += e;
Packit 9c3e7e
				res->err_updates++;
Packit 9c3e7e
				res->err /= res->err_updates;
Packit 9c3e7e
			} else {
Packit 9c3e7e
				res->err += ERR_SMOOTH * (e - res->err);
Packit 9c3e7e
			}
Packit 9c3e7e
		}
Packit 9c3e7e
Packit 9c3e7e
		for (; i < n; i++) {
Packit 9c3e7e
			/* Iterate points from newest to oldest */
Packit 9c3e7e
			l = (MAX_POINTS + s->last_point - i) % MAX_POINTS;
Packit 9c3e7e
Packit 9c3e7e
			x = (int64_t)(s->points[l].x - s->reference.x);
Packit 9c3e7e
			y = (int64_t)(s->points[l].y - s->reference.y);
Packit 9c3e7e
			w = s->points[l].w;
Packit 9c3e7e
Packit 9c3e7e
			x_sum += x * w;
Packit 9c3e7e
			y_sum += y * w;
Packit 9c3e7e
			xy_sum += x * y * w;
Packit 9c3e7e
			x2_sum += x * x * w;
Packit 9c3e7e
			w_sum += w;
Packit 9c3e7e
		}
Packit 9c3e7e
Packit 9c3e7e
		/* Get new intercept and slope */
Packit 9c3e7e
		res->slope = (xy_sum - x_sum * y_sum / w_sum) /
Packit 9c3e7e
				(x2_sum - x_sum * x_sum / w_sum);
Packit 9c3e7e
		res->intercept = (y_sum - res->slope * x_sum) / w_sum;
Packit 9c3e7e
	}
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void update_size(struct linreg_servo *s)
Packit 9c3e7e
{
Packit 9c3e7e
	struct result *res;
Packit 9c3e7e
	double best_err;
Packit 9c3e7e
	int size, best_size;
Packit 9c3e7e
Packit 9c3e7e
	/* Find largest size with smallest prediction error */
Packit 9c3e7e
Packit 9c3e7e
	best_size = 0;
Packit 9c3e7e
	best_err = 0.0;
Packit 9c3e7e
Packit 9c3e7e
	for (size = MIN_SIZE; size <= MAX_SIZE; size++) {
Packit 9c3e7e
		res = &s->results[size - MIN_SIZE];
Packit 9c3e7e
		if ((!best_size && res->slope) ||
Packit 9c3e7e
		    (best_err * ERR_EQUALS > res->err &&
Packit 9c3e7e
		     res->err_updates >= ERR_INITIAL_UPDATES)) {
Packit 9c3e7e
			best_size = size;
Packit 9c3e7e
			best_err = res->err;
Packit 9c3e7e
		}
Packit 9c3e7e
	}
Packit 9c3e7e
Packit 9c3e7e
	s->size = best_size;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static double linreg_sample(struct servo *servo,
Packit 9c3e7e
			    int64_t offset,
Packit 9c3e7e
			    uint64_t local_ts,
Packit 9c3e7e
			    double weight,
Packit 9c3e7e
			    enum servo_state *state)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s = container_of(servo, struct linreg_servo, servo);
Packit 9c3e7e
	struct result *res;
Packit 9c3e7e
	int corr_interval;
Packit 9c3e7e
Packit 9c3e7e
	/*
Packit 9c3e7e
	 * The current time and the time when will be the frequency of the
Packit 9c3e7e
	 * clock actually updated is assumed here to be equal to local_ts
Packit 9c3e7e
	 * (which is the time stamp of the received sync message). As long as
Packit 9c3e7e
	 * the differences are smaller than the update interval, the loop
Packit 9c3e7e
	 * should be robust enough to handle this simplification.
Packit 9c3e7e
	 */
Packit 9c3e7e
Packit 9c3e7e
	update_reference(s, local_ts);
Packit 9c3e7e
	add_sample(s, offset, weight);
Packit 9c3e7e
	regress(s);
Packit 9c3e7e
Packit 9c3e7e
	update_size(s);
Packit 9c3e7e
Packit 9c3e7e
	if (s->size < MIN_SIZE) {
Packit 9c3e7e
		/* Not enough points, wait for more */
Packit 9c3e7e
		*state = SERVO_UNLOCKED;
Packit 9c3e7e
		return -s->clock_freq;
Packit 9c3e7e
	}
Packit 9c3e7e
Packit 9c3e7e
	res = &s->results[s->size - MIN_SIZE];
Packit 9c3e7e
Packit 9c3e7e
	pr_debug("linreg: points %d slope %.9f intercept %.0f err %.0f",
Packit 9c3e7e
		 1 << s->size, res->slope, res->intercept, res->err);
Packit 9c3e7e
Packit 9c3e7e
	if ((servo->first_update &&
Packit 9c3e7e
	     servo->first_step_threshold &&
Packit 9c3e7e
	     servo->first_step_threshold < fabs(res->intercept)) ||
Packit 9c3e7e
	    (servo->step_threshold &&
Packit 9c3e7e
	     servo->step_threshold < fabs(res->intercept))) {
Packit 9c3e7e
		/* The clock will be stepped by offset */
Packit 9c3e7e
		move_reference(s, 0, -offset);
Packit 9c3e7e
		s->last_update -= offset;
Packit 9c3e7e
		*state = SERVO_JUMP;
Packit 9c3e7e
	} else {
Packit 9c3e7e
		*state = SERVO_LOCKED;
Packit 9c3e7e
	}
Packit 9c3e7e
Packit 9c3e7e
	/* Set clock frequency to the slope */
Packit 9c3e7e
	s->clock_freq = 1e9 * (res->slope - 1.0);
Packit 9c3e7e
Packit 9c3e7e
	/*
Packit 9c3e7e
	 * Adjust the frequency to correct the time offset. Use longer
Packit 9c3e7e
	 * correction interval with larger sizes to reduce the frequency error.
Packit 9c3e7e
	 * The update interval is assumed to be not affected by the frequency
Packit 9c3e7e
	 * adjustment. If it is (e.g. phc2sys controlling the system clock), a
Packit 9c3e7e
	 * correction slowing down the clock will result in an overshoot. With
Packit 9c3e7e
	 * the system clock's maximum adjustment of 10% that's acceptable.
Packit 9c3e7e
	 */
Packit 9c3e7e
	corr_interval = s->size <= 4 ? 1 : s->size / 2;
Packit 9c3e7e
	s->clock_freq += res->intercept / s->update_interval / corr_interval;
Packit 9c3e7e
Packit 9c3e7e
	/* Clamp the frequency to the allowed maximum */
Packit 9c3e7e
	if (s->clock_freq > servo->max_frequency)
Packit 9c3e7e
		s->clock_freq = servo->max_frequency;
Packit 9c3e7e
	else if (s->clock_freq < -servo->max_frequency)
Packit 9c3e7e
		s->clock_freq = -servo->max_frequency;
Packit 9c3e7e
Packit 9c3e7e
	s->frequency_ratio = res->slope / (1.0 + s->clock_freq / 1e9);
Packit 9c3e7e
Packit 9c3e7e
	return -s->clock_freq;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void linreg_sync_interval(struct servo *servo, double interval)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s = container_of(servo, struct linreg_servo, servo);
Packit 9c3e7e
Packit 9c3e7e
	s->update_interval = interval;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void linreg_reset(struct servo *servo)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s = container_of(servo, struct linreg_servo, servo);
Packit 9c3e7e
	unsigned int i;
Packit 9c3e7e
Packit 9c3e7e
	s->num_points = 0;
Packit 9c3e7e
	s->last_update = 0;
Packit 9c3e7e
	s->size = 0;
Packit 9c3e7e
	s->frequency_ratio = 1.0;
Packit 9c3e7e
Packit 9c3e7e
	for (i = MIN_SIZE; i <= MAX_SIZE; i++) {
Packit 9c3e7e
		s->results[i - MIN_SIZE].slope = 0.0;
Packit 9c3e7e
		s->results[i - MIN_SIZE].err_updates = 0;
Packit 9c3e7e
	}
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static double linreg_rate_ratio(struct servo *servo)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s = container_of(servo, struct linreg_servo, servo);
Packit 9c3e7e
Packit 9c3e7e
	return s->frequency_ratio;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
static void linreg_leap(struct servo *servo, int leap)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s = container_of(servo, struct linreg_servo, servo);
Packit 9c3e7e
Packit 9c3e7e
	/*
Packit 9c3e7e
	 * Move reference when leap second is applied to the reference
Packit 9c3e7e
	 * time as if the clock was stepped in the opposite direction
Packit 9c3e7e
	 */
Packit 9c3e7e
	if (s->leap && !leap)
Packit 9c3e7e
		move_reference(s, 0, s->leap * 1000000000);
Packit 9c3e7e
Packit 9c3e7e
	s->leap = leap;
Packit 9c3e7e
}
Packit 9c3e7e
Packit 9c3e7e
struct servo *linreg_servo_create(int fadj)
Packit 9c3e7e
{
Packit 9c3e7e
	struct linreg_servo *s;
Packit 9c3e7e
Packit 9c3e7e
	s = calloc(1, sizeof(*s));
Packit 9c3e7e
	if (!s)
Packit 9c3e7e
		return NULL;
Packit 9c3e7e
Packit 9c3e7e
	s->servo.destroy = linreg_destroy;
Packit 9c3e7e
	s->servo.sample = linreg_sample;
Packit 9c3e7e
	s->servo.sync_interval = linreg_sync_interval;
Packit 9c3e7e
	s->servo.reset = linreg_reset;
Packit 9c3e7e
	s->servo.rate_ratio = linreg_rate_ratio;
Packit 9c3e7e
	s->servo.leap = linreg_leap;
Packit 9c3e7e
Packit 9c3e7e
	s->clock_freq = -fadj;
Packit 9c3e7e
	s->frequency_ratio = 1.0;
Packit 9c3e7e
Packit 9c3e7e
	return &s->servo;
Packit 9c3e7e
}