| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- #include "welford_mean.h"
- #include <math.h>
- #include <stdint.h>
- static inline float kahanSummation(float sum_previous, float input,
- float *accumulator) {
- const float y = input - *accumulator;
- const float t = sum_previous + y;
- *accumulator = (t - sum_previous) - y;
- return t;
- }
- void welford_mean_reset(WelfordMean_t *wm) {
- if (wm) {
- wm->_mean = 0.0f;
- wm->_M2 = 0.0f;
- wm->_mean_accum = 0.0f;
- wm->_M2_accum = 0.0f;
- wm->_count = 0;
- }
- }
- bool welford_mean_valid(WelfordMean_t *wm) {
- if (wm == NULL) {
- return false;
- }
- return wm->_count > 2;
- }
- bool welford_mean_updata(WelfordMean_t *wm, float new_val) {
- if (wm == NULL) {
- return false;
- }
- if (wm->_count == 0) {
- welford_mean_reset(wm);
- wm->_count = 1;
- wm->_mean = new_val;
- return false;
- } else if (wm->_count == UINT16_MAX) {
- // count overflow
- // reset count, but maintain mean and variance
- wm->_M2 = wm->_M2 / wm->_count;
- wm->_M2_accum = 0;
- wm->_count = 1;
- } else {
- wm->_count++;
- }
- // mean accumulates the mean of the entire dataset
- // delta can be very small compared to the mean, use algorithm to minimise
- // numerical error
- float delta = new_val - wm->_mean;
- float mean_change = delta / wm->_count;
- wm->_mean = kahanSummation(wm->_mean, mean_change, &wm->_mean_accum);
- // M2 aggregates the squared distance from the mean
- // count aggregates the number of samples seen so far
- const float M2_change = delta * (new_val - wm->_mean);
- wm->_M2 = kahanSummation(wm->_M2, M2_change, &wm->_M2_accum);
- // protect against floating point precision causing negative variances
- wm->_M2 = wm->_M2 > 0 ? wm->_M2 : 0;
- if (!isfinite(wm->_mean) || !isfinite(wm->_M2)) {
- welford_mean_reset(wm);
- return false;
- }
- return welford_mean_valid(wm);
- }
- int welford_mean_get_count(WelfordMean_t *wm) {
- if (wm) {
- return wm->_count;
- } else {
- return 0;
- }
- }
- float welford_mean_get_mean(WelfordMean_t *wm) {
- if (wm) {
- return wm->_mean;
- } else {
- return NAN;
- }
- }
- float welford_mean_get_variance(WelfordMean_t *wm) {
- if (wm) {
- return wm->_M2 / (wm->_count - 1);
- } else {
- return NAN;
- }
- }
- float welford_mean_get_stddev(WelfordMean_t *wm) {
- if (wm) {
- return sqrtf(welford_mean_get_variance(wm));
- } else {
- return NAN;
- }
- }
- bool welford_mean_vector3f_valid(WelfordMeanVector3f_t *wm) {
- if (wm == NULL) {
- return false;
- }
- return wm->_count > 2;
- }
- void welford_mean_vector3f_reset(WelfordMeanVector3f_t *wm) {
- if (wm) {
- for (int i = 0; i < 3; i++) {
- wm->_mean[i] = 0.0f;
- wm->_mean_accum[i] = 0.0f;
- for (int j = 0; j < 3; j++) {
- wm->_M2[i][j] = 0.0f;
- wm->_M2_accum[i][j] = 0.0f;
- }
- }
- wm->_count = 0;
- }
- }
- bool welford_mean_vector3f_updata(WelfordMeanVector3f_t *wm, float new_val[3]) {
- if (wm == NULL || new_val == NULL) {
- return false;
- }
- if (wm->_count == 0) {
- welford_mean_vector3f_reset(wm);
- wm->_count = 1;
- for (int i = 0; i < 3; i++) {
- wm->_mean[i] = new_val[i];
- }
- return false;
- } else if (wm->_count == UINT16_MAX) {
- // count overflow
- // reset count, but maintain mean and variance
- for (int i = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++) {
- wm->_M2[i][j] = wm->_M2[i][j] / wm->_count;
- wm->_M2_accum[i][j] = 0;
- }
- }
- wm->_count = 1;
- } else {
- wm->_count++;
- }
- // mean
- // accumulates the mean of the entire dataset
- // use Kahan summation because delta can be very small compared to the mean
- float delta[3];
- for (int i = 0; i < 3; i++) {
- delta[i] = new_val[i] - wm->_mean[i];
- }
- float y[3];
- float t[3];
- for (int i = 0; i < 3; i++) {
- y[i] = delta[i] - wm->_mean_accum[i];
- t[i] = wm->_mean[i] + y[i];
- wm->_mean_accum[i] = (t[i] - wm->_mean[i]) - y[i];
- wm->_mean[i] = t[i];
- }
- for (int i = 0; i < 3; ++i) {
- if (!isfinite(wm->_mean[i])) {
- welford_mean_vector3f_reset(wm);
- return false;
- }
- }
- // covariance
- // Kahan summation (upper triangle only)
- // eg C(x,y) += dx * (y - mean_y)
- float m2_change[3][3];
- for (size_t r = 0; r < 3; r++) {
- for (size_t c = r; c < 3; c++) {
- m2_change[r][c] = delta[r] * (new_val[c] - wm->_mean[c]);
- }
- }
- for (size_t r = 0; r < 3; r++) {
- for (size_t c = r; c < 3; c++) {
- float y = m2_change[r][c] - wm->_M2_accum[r][c];
- float t = wm->_M2[r][c] + y;
- wm->_M2_accum[r][c] = (t - wm->_M2[r][c]) - y;
- wm->_M2[r][c] = t;
- }
- // protect against floating point precision causing negative variances
- if (wm->_M2[r][r] < 0) {
- wm->_M2[r][r] = 0;
- }
- }
- // make symmetric
- for (size_t r = 0; r < 3; r++) {
- for (size_t c = r + 1; c < 3; c++) {
- wm->_M2[c][r] = wm->_M2[r][c];
- }
- }
- for (size_t r = 0; r < 3; r++) {
- for (size_t c = 0; c < 3; c++) {
- if (!isfinite(wm->_M2[r][c])) {
- welford_mean_vector3f_reset(wm);
- return false;
- }
- }
- }
- return welford_mean_vector3f_valid(wm);
- }
- int welford_mean_vector3f_get_count(WelfordMeanVector3f_t *wm) {
- if (wm == NULL) {
- return 0;
- }
- return wm->_count;
- }
- bool welford_mean_vector3f_get_mean(WelfordMeanVector3f_t *wm, float mean[3]) {
- if (wm == NULL || mean == NULL) {
- return false;
- }
- for (int i = 0; i < 3; i++) {
- mean[i] = wm->_mean[i];
- }
- return true;
- }
- bool welford_mean_vector3f_get_variance(WelfordMeanVector3f_t *wm,
- float variance[3]) {
- if (wm == NULL || variance == NULL) {
- return false;
- }
- for (int i = 0; i < 3; i++) {
- variance[i] = wm->_M2[i][i] / (wm->_count - 1);
- }
- return true;
- }
- float welford_mean_vector3f_get_covariance(WelfordMeanVector3f_t *wm, int x,
- int y) {
- if (wm == NULL || x < 0 || x > 2 || y < 0 || y > 2) {
- return NAN;
- }
- return wm->_M2[x][y] / (wm->_count - 1);
- }
|