Commit e7ba5e63 authored by Mina Almasry's avatar Mina Almasry Committed by Commit Bot

[Chromecast] Move weighted_moving_linear_regression and dependencies to base

Will be used in to be implemented CMA backend.

BUG=internal b/73746352
TEST=Build

Change-Id: I08d520edf54cbd8658c6112eeb930e7e68fd3d6a
Reviewed-on: https://chromium-review.googlesource.com/933584Reviewed-by: default avatarStephen Lanham <slan@chromium.org>
Commit-Queue: Mina Almasry <almasrymina@chromium.org>
Cr-Commit-Position: refs/heads/master@{#538868}
parent ef7dd40d
...@@ -71,6 +71,12 @@ cast_source_set("base") { ...@@ -71,6 +71,12 @@ cast_source_set("base") {
"process_utils.h", "process_utils.h",
"serializers.cc", "serializers.cc",
"serializers.h", "serializers.h",
"statistics/weighted_mean.cc",
"statistics/weighted_mean.h",
"statistics/weighted_moving_average.cc",
"statistics/weighted_moving_average.h",
"statistics/weighted_moving_linear_regression.cc",
"statistics/weighted_moving_linear_regression.h",
"system_time_change_notifier.cc", "system_time_change_notifier.cc",
"system_time_change_notifier.h", "system_time_change_notifier.h",
"task_runner_impl.cc", "task_runner_impl.cc",
...@@ -147,6 +153,8 @@ test("cast_base_unittests") { ...@@ -147,6 +153,8 @@ test("cast_base_unittests") {
"path_utils_unittest.cc", "path_utils_unittest.cc",
"process_utils_unittest.cc", "process_utils_unittest.cc",
"serializers_unittest.cc", "serializers_unittest.cc",
"statistics/weighted_moving_average_unittest.cc",
"statistics/weighted_moving_linear_regression_unittest.cc",
"system_time_change_notifier_unittest.cc", "system_time_change_notifier_unittest.cc",
"thread_health_checker_unittest.cc", "thread_health_checker_unittest.cc",
] ]
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/base/statistics/weighted_mean.h"
namespace chromecast {
WeightedMean::WeightedMean()
: weighted_mean_(0), variance_sum_(0), sum_weights_(0) {}
WeightedMean::~WeightedMean() {}
void WeightedMean::AddSample(int64_t value, double weight) {
double old_sum_weights = sum_weights_;
sum_weights_ += weight;
if (sum_weights_ == 0) {
weighted_mean_ = 0;
variance_sum_ = 0;
} else {
double delta = value - weighted_mean_;
double mean_change = delta * weight / sum_weights_;
weighted_mean_ += mean_change;
variance_sum_ += old_sum_weights * delta * mean_change;
}
}
} // namespace chromecast
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_BASE_STATISTICS_WEIGHTED_MEAN_H_
#define CHROMECAST_BASE_STATISTICS_WEIGHTED_MEAN_H_
#include <stdint.h>
namespace chromecast {
// Calculates the weighted mean (and variance) of a set of values. Values can be
// added to or removed from the mean.
class WeightedMean {
public:
WeightedMean();
~WeightedMean();
double weighted_mean() const { return weighted_mean_; }
// The weighted variance should be calculated as variance_sum()/sum_weights().
double variance_sum() const { return variance_sum_; }
double sum_weights() const { return sum_weights_; }
// Adds |value| to the mean if |weight| is positive. Removes |value| from
// the mean if |weight| is negative. Has no effect if |weight| is 0.
void AddSample(int64_t value, double weight);
private:
double weighted_mean_;
double variance_sum_;
double sum_weights_;
};
} // namespace chromecast
#endif // CHROMECAST_BASE_STATISTICS_WEIGHTED_MEAN_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/base/statistics/weighted_moving_average.h"
#include <math.h>
#include "base/logging.h"
namespace chromecast {
WeightedMovingAverage::WeightedMovingAverage(int64_t max_x_range)
: max_x_range_(max_x_range) {
DCHECK_GE(max_x_range_, 0);
}
WeightedMovingAverage::~WeightedMovingAverage() {}
void WeightedMovingAverage::AddSample(int64_t x, int64_t y, double weight) {
DCHECK_GE(weight, 0);
if (!samples_.empty())
DCHECK_GE(x, samples_.back().x);
Sample sample = {x, y, weight};
samples_.push_back(sample);
mean_.AddSample(y, weight);
// Remove old samples.
while (x - samples_.front().x > max_x_range_) {
const Sample& old_sample = samples_.front();
mean_.AddSample(old_sample.y, -old_sample.weight);
samples_.pop_front();
}
DCHECK(!samples_.empty());
}
bool WeightedMovingAverage::Average(int64_t* average, double* error) const {
if (samples_.empty() || mean_.sum_weights() == 0)
return false;
*average = static_cast<int64_t>(round(mean_.weighted_mean()));
*error = sqrt(mean_.variance_sum() / mean_.sum_weights());
return true;
}
void WeightedMovingAverage::Clear() {
samples_.clear();
mean_ = WeightedMean();
}
} // namespace chromecast
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_BASE_STATISTICS_WEIGHTED_MOVING_AVERAGE_H_
#define CHROMECAST_BASE_STATISTICS_WEIGHTED_MOVING_AVERAGE_H_
#include <stdint.h>
#include <deque>
#include "base/macros.h"
#include "chromecast/base/statistics/weighted_mean.h"
namespace chromecast {
// Calculates the weighted moving average of recent points. The points
// do not need to be evenly distributed on the X axis, but the X coordinate
// is assumed to be generally increasing.
//
// Whenever a new sample is added using AddSample(), old samples whose
// x coordinates are farther than |max_x_range_| from the new sample's
// x coordinate will be removed from the average. Note that |max_x_range_|
// must be non-negative.
class WeightedMovingAverage {
public:
explicit WeightedMovingAverage(int64_t max_x_range);
~WeightedMovingAverage();
int64_t max_x_range() const { return max_x_range_; }
// Returns the current number of samples that are in the weighted average.
size_t num_samples() const { return samples_.size(); }
// Adds an (x, y) sample with the provided weight to the average.
// |weight| should be non-negative.
void AddSample(int64_t x, int64_t y, double weight);
// Gets the current average and standard error.
// Returns |true| if the average exists, |false| otherwise. If the average
// does not exist, |average| and |error| are not modified.
bool Average(int64_t* average, double* error) const;
// Clears all current samples from the moving average.
void Clear();
private:
struct Sample {
int64_t x;
int64_t y;
double weight;
};
const int64_t max_x_range_;
std::deque<Sample> samples_;
WeightedMean mean_;
DISALLOW_COPY_AND_ASSIGN(WeightedMovingAverage);
};
} // namespace chromecast
#endif // CHROMECAST_BASE_STATISTICS_WEIGHTED_MOVING_AVERAGE_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include "chromecast/base/statistics/weighted_moving_average.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
TEST(WeightedMovingAverageTest, NoSamples) {
WeightedMovingAverage averager(0);
int64_t avg = 12345;
double error = 12345.0;
EXPECT_FALSE(averager.Average(&avg, &error));
EXPECT_EQ(12345, avg);
EXPECT_EQ(12345.0, error);
}
TEST(WeightedMovingAverageTest, ZeroWeight) {
WeightedMovingAverage averager(0);
for (int s = 1; s <= 5; ++s)
averager.AddSample(0, s, 0.0);
int64_t avg = 12345;
double error = 12345.0;
EXPECT_FALSE(averager.Average(&avg, &error));
EXPECT_EQ(12345, avg);
EXPECT_EQ(12345.0, error);
}
TEST(WeightedMovingAverageTest, AverageOneValue) {
int64_t value = 1;
WeightedMovingAverage averager(0);
averager.AddSample(0, value, 1.0);
int64_t avg = 0;
double error = 1.0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(value, avg);
EXPECT_EQ(0.0, error);
}
TEST(WeightedMovingAverageTest, AverageSeveralUnweightedValues) {
WeightedMovingAverage averager(0);
for (int s = 1; s <= 5; ++s)
averager.AddSample(0, s, 1.0);
int64_t avg = 0;
double error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(3, avg);
EXPECT_NEAR(sqrt(2), error, 1e-9);
}
TEST(WeightedMovingAverageTest, Clear) {
WeightedMovingAverage averager(0);
for (int s = 1; s <= 5; ++s)
averager.AddSample(0, s, 1.0);
int64_t avg = 0;
double error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(3, avg);
EXPECT_NEAR(sqrt(2), error, 1e-9);
averager.Clear();
EXPECT_FALSE(averager.Average(&avg, &error));
for (int s = 1; s <= 5; ++s)
averager.AddSample(0, s, 1.0);
avg = 0;
error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(3, avg);
EXPECT_NEAR(sqrt(2), error, 1e-9);
}
TEST(WeightedMovingAverageTest, AverageSeveralWeightedValues) {
WeightedMovingAverage averager(0);
averager.AddSample(0, 1, 2.0);
averager.AddSample(0, 2, 1.0);
averager.AddSample(0, 3, 0.0);
averager.AddSample(0, 4, 1.0);
averager.AddSample(0, 5, 2.0);
int64_t avg = 0;
double error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(3, avg);
EXPECT_NEAR(sqrt(3), error, 1e-9);
}
TEST(WeightedMovingAverageTest, DropOldValues) {
WeightedMovingAverage averager(1);
for (int s = 0; s < 10; ++s)
averager.AddSample(s, 100, 5.0);
averager.AddSample(10, 1, 1.0);
averager.AddSample(11, 3, 1.0);
int64_t avg = 0;
double error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(2, avg);
EXPECT_DOUBLE_EQ(1.0, error);
}
TEST(WeightedMovingAverageTest, DropOldValuesUneven) {
WeightedMovingAverage averager(5);
for (int s = 0; s < 10; ++s)
averager.AddSample(s * s, 100, 5.0);
averager.AddSample(100, 1, 1.0);
averager.AddSample(105, 3, 1.0);
int64_t avg = 0;
double error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(2, avg);
EXPECT_DOUBLE_EQ(1.0, error);
}
TEST(WeightedMovingAverageTest, DropOldValuesByAddingZeroWeightValues) {
WeightedMovingAverage averager(5);
for (int s = 0; s < 10; ++s)
averager.AddSample(s, 1, 5.0);
// Adding values with weight 0 still drops old values.
for (int s = 11; s < 15; ++s)
averager.AddSample(s, 100, 0.0);
averager.AddSample(15, 10, 1.0);
int64_t avg = 0;
double error = 0;
EXPECT_TRUE(averager.Average(&avg, &error));
EXPECT_EQ(10, avg);
EXPECT_DOUBLE_EQ(0.0, error);
}
} // namespace chromecast
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/base/statistics/weighted_moving_linear_regression.h"
#include <math.h>
#include <algorithm>
#include "base/logging.h"
namespace chromecast {
WeightedMovingLinearRegression::WeightedMovingLinearRegression(
int64_t max_x_range)
: max_x_range_(max_x_range),
covariance_(0),
slope_(0),
slope_variance_(0),
intercept_variance_(0),
has_estimate_(false) {
DCHECK_GE(max_x_range_, 0);
}
WeightedMovingLinearRegression::~WeightedMovingLinearRegression() {}
void WeightedMovingLinearRegression::AddSample(int64_t x,
int64_t y,
double weight) {
DCHECK_GE(weight, 0);
if (!samples_.empty())
DCHECK_GE(x, samples_.back().x);
UpdateSet(x, y, weight);
Sample sample = {x, y, weight};
samples_.push(sample);
// Remove old samples.
while (x - samples_.front().x > max_x_range_) {
const Sample& old_sample = samples_.front();
UpdateSet(old_sample.x, old_sample.y, -old_sample.weight);
samples_.pop();
}
DCHECK(!samples_.empty());
if (samples_.size() <= 2 || x_mean_.sum_weights() == 0 ||
x_mean_.variance_sum() == 0) {
has_estimate_ = false;
return;
}
slope_ = covariance_ / x_mean_.variance_sum();
double residual_sum_squares =
(covariance_ * covariance_) / x_mean_.variance_sum();
double mean_squared_error =
(y_mean_.variance_sum() - residual_sum_squares) / (samples_.size() - 2);
slope_variance_ = std::max(0.0, mean_squared_error / x_mean_.variance_sum());
intercept_variance_ = std::max(
0.0, (slope_variance_ * x_mean_.variance_sum()) / x_mean_.sum_weights());
has_estimate_ = true;
}
bool WeightedMovingLinearRegression::EstimateY(int64_t x,
int64_t* y,
double* error) const {
if (!has_estimate_)
return false;
double x_diff = x - x_mean_.weighted_mean();
double y_estimate = y_mean_.weighted_mean() + (slope_ * x_diff);
*y = static_cast<int64_t>(round(y_estimate));
*error = sqrt(intercept_variance_ + (slope_variance_ * x_diff * x_diff));
return true;
}
bool WeightedMovingLinearRegression::EstimateSlope(double* slope,
double* error) const {
if (!has_estimate_)
return false;
*slope = slope_;
*error = sqrt(slope_variance_);
return true;
}
void WeightedMovingLinearRegression::UpdateSet(int64_t x,
int64_t y,
double weight) {
double old_y_mean = y_mean_.weighted_mean();
x_mean_.AddSample(x, weight);
y_mean_.AddSample(y, weight);
covariance_ += weight * (x - x_mean_.weighted_mean()) * (y - old_y_mean);
}
} // namespace chromecast
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_BASE_STATISTICS_WEIGHTED_MOVING_LINEAR_REGRESSION_H_
#define CHROMECAST_BASE_STATISTICS_WEIGHTED_MOVING_LINEAR_REGRESSION_H_
#include <stdint.h>
#include <queue>
#include "base/macros.h"
#include "chromecast/base/statistics/weighted_mean.h"
namespace chromecast {
// Performs linear regression over a set of weighted (x, y) samples.
// Calculates a weighted moving average over a set of weighted (x, y) points.
// The points do not need to be evenly distributed on the X axis, but the
// X coordinate is assumed to be generally increasing.
//
// Whenever a new sample is added using AddSample(), old samples whose
// x coordinates are farther than |max_x_range_| from the new sample's
// x coordinate will be removed from the regression. Note that |max_x_range_|
// must be non-negative.
class WeightedMovingLinearRegression {
public:
explicit WeightedMovingLinearRegression(int64_t max_x_range);
~WeightedMovingLinearRegression();
// Returns the current number of samples that are in the regression.
size_t num_samples() const { return samples_.size(); }
// Adds a weighted (x, y) sample to the set. Note that |weight|
// should be positive.
void AddSample(int64_t x, int64_t y, double weight);
// Gets a y value estimate from the linear regression: y = a*x + b, where
// a and b are the slope and intercept estimates from the regression. The
// standard error of the resulting y estimate is also provided.
// Returns false if the y value cannot be estimated, in which case y and
// |error| are not modified. Returns true otherwise.
bool EstimateY(int64_t x, int64_t* y, double* error) const;
// Gets the current estimated slope and slope error from the linear
// regression. Returns false if the slope cannot be estimated, in which
// case |slope| and |error| are not modified. Returns true otherwise.
bool EstimateSlope(double* slope, double* error) const;
private:
struct Sample {
int64_t x;
int64_t y;
double weight;
};
// Adds (x, y) to the set if |weight| is positive; removes (x, y) from the
// set if |weight| is negative.
void UpdateSet(int64_t x, int64_t y, double weight);
const int64_t max_x_range_;
WeightedMean x_mean_;
WeightedMean y_mean_;
double covariance_;
std::queue<Sample> samples_;
double slope_;
double slope_variance_;
double intercept_variance_;
bool has_estimate_;
DISALLOW_COPY_AND_ASSIGN(WeightedMovingLinearRegression);
};
} // namespace chromecast
#endif // CHROMECAST_BASE_STATISTICS_WEIGHTED_MOVING_LINEAR_REGRESSION_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/base/statistics/weighted_moving_linear_regression.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
TEST(WeightedMovingLinearRegressionTest, NotEnoughSamples) {
for (int num_samples = 0; num_samples <= 2; ++num_samples) {
WeightedMovingLinearRegression linear(1e6);
for (int s = 0; s < num_samples; ++s)
linear.AddSample(s, s, 1.0);
int64_t y = 12345;
double error = 12345.0;
EXPECT_FALSE(linear.EstimateY(0, &y, &error));
EXPECT_EQ(12345, y);
EXPECT_EQ(12345.0, error);
double slope = 12345.0;
EXPECT_FALSE(linear.EstimateSlope(&slope, &error));
EXPECT_EQ(12345.0, slope);
EXPECT_EQ(12345.0, error);
}
}
TEST(WeightedMovingLinearRegressionTest, NoXVariance) {
WeightedMovingLinearRegression linear(1e6);
for (int s = 0; s < 10; ++s)
linear.AddSample(0, s, 1.0);
int64_t y = 12345;
double error = 12345.0;
EXPECT_FALSE(linear.EstimateY(0, &y, &error));
EXPECT_EQ(12345, y);
EXPECT_EQ(12345.0, error);
double slope = 12345.0;
EXPECT_FALSE(linear.EstimateSlope(&slope, &error));
EXPECT_EQ(12345.0, slope);
EXPECT_EQ(12345.0, error);
}
TEST(WeightedMovingLinearRegressionTest, ZeroWeight) {
WeightedMovingLinearRegression linear(1e6);
for (int s = 0; s < 10; ++s)
linear.AddSample(s, s, 0.0);
int64_t y = 12345;
double error = 12345.0;
EXPECT_FALSE(linear.EstimateY(0, &y, &error));
EXPECT_EQ(12345, y);
EXPECT_EQ(12345.0, error);
double slope = 12345.0;
EXPECT_FALSE(linear.EstimateSlope(&slope, &error));
EXPECT_EQ(12345.0, slope);
EXPECT_EQ(12345.0, error);
}
TEST(WeightedMovingLinearRegressionTest, SimpleLine) {
WeightedMovingLinearRegression linear(1e6);
for (int s = 0; s < 3; ++s)
linear.AddSample(s, s, 1.0);
int64_t y;
double error;
EXPECT_TRUE(linear.EstimateY(20, &y, &error));
EXPECT_EQ(20, y);
EXPECT_DOUBLE_EQ(0.0, error);
double slope;
EXPECT_TRUE(linear.EstimateSlope(&slope, &error));
EXPECT_DOUBLE_EQ(1.0, slope);
EXPECT_DOUBLE_EQ(0.0, error);
}
TEST(WeightedMovingLinearRegressionTest, SimpleLineHighX) {
WeightedMovingLinearRegression linear(1e6);
for (int s = 0; s < 10; ++s)
linear.AddSample(1000000000 + s, s, 1.0);
int64_t y;
double error;
EXPECT_TRUE(linear.EstimateY(0, &y, &error));
EXPECT_EQ(-1000000000, y);
EXPECT_DOUBLE_EQ(0.0, error);
EXPECT_TRUE(linear.EstimateY(1000000020, &y, &error));
EXPECT_EQ(20, y);
EXPECT_DOUBLE_EQ(0.0, error);
double slope;
EXPECT_TRUE(linear.EstimateSlope(&slope, &error));
EXPECT_DOUBLE_EQ(1.0, slope);
EXPECT_DOUBLE_EQ(0.0, error);
}
TEST(WeightedMovingLinearRegressionTest, Weighted) {
WeightedMovingLinearRegression linear(1e6);
// Add some weight 1.0 points on the line y = x/2, and some weight 2.0 points
// on the line y = x/2 + 4.5.
for (int s = 0; s < 1000; ++s) {
linear.AddSample(2 * s, s, 1.0);
linear.AddSample(2 * s + 1, s + 5, 2.0);
}
// The resulting estimate should be y = x/2 + 3.
int64_t y;
double error;
EXPECT_TRUE(linear.EstimateY(20, &y, &error));
EXPECT_EQ(13, y);
EXPECT_TRUE(linear.EstimateY(-20, &y, &error));
EXPECT_EQ(-7, y);
EXPECT_NEAR(0.0, error, 0.1);
double slope;
EXPECT_TRUE(linear.EstimateSlope(&slope, &error));
EXPECT_NEAR(0.5, slope, 0.001);
EXPECT_NEAR(0.0, error, 0.001);
}
TEST(WeightedMovingLinearRegressionTest, DropOldSamples) {
WeightedMovingLinearRegression linear(1999);
// First add some points that will fall outside of the window.
for (int s = -1000; s < 0; ++s)
linear.AddSample(s, 0, 1.0);
// Add some weight 1.0 points on the line y = x/2, and some weight 2.0 points
// on the line y = x/2 + 4.5.
for (int s = 0; s < 1000; ++s) {
linear.AddSample(2 * s, s, 1.0);
linear.AddSample(2 * s + 1, s + 5, 2.0);
}
// The resulting estimate should be y = x/2 + 3.
int64_t y;
double error;
EXPECT_TRUE(linear.EstimateY(20, &y, &error));
EXPECT_EQ(13, y);
EXPECT_TRUE(linear.EstimateY(-20, &y, &error));
EXPECT_EQ(-7, y);
EXPECT_NEAR(0.0, error, 0.1);
double slope;
EXPECT_TRUE(linear.EstimateSlope(&slope, &error));
EXPECT_NEAR(0.5, slope, 0.001);
EXPECT_NEAR(0.0, error, 0.001);
}
} // namespace chromecast
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment