@@ -161,6 +161,21 @@ VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) {
161161 // of the velocity when the finger is released.
162162 return new LeastSquaresVelocityTrackerStrategy (3 );
163163 }
164+ if (!strcmp (" wlsq2-delta" , strategy)) {
165+ // 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL
166+ return new LeastSquaresVelocityTrackerStrategy (2 ,
167+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
168+ }
169+ if (!strcmp (" wlsq2-central" , strategy)) {
170+ // 2nd order weighted least squares, central weighting. Quality: EXPERIMENTAL
171+ return new LeastSquaresVelocityTrackerStrategy (2 ,
172+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
173+ }
174+ if (!strcmp (" wlsq2-recent" , strategy)) {
175+ // 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL
176+ return new LeastSquaresVelocityTrackerStrategy (2 ,
177+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
178+ }
164179 if (!strcmp (" int1" , strategy)) {
165180 // 1st order integrating filter. Quality: GOOD.
166181 // Not as good as 'lsq2' because it cannot estimate acceleration but it is
@@ -327,8 +342,9 @@ bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
327342const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON;
328343const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE;
329344
330- LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy (uint32_t degree) :
331- mDegree (degree) {
345+ LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy (
346+ uint32_t degree, Weighting weighting) :
347+ mDegree (degree), mWeighting (weighting) {
332348 clear ();
333349}
334350
@@ -366,10 +382,23 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet3
366382 *
367383 * Returns true if a solution is found, false otherwise.
368384 *
369- * The input consists of two vectors of data points X and Y with indices 0..m-1.
385+ * The input consists of two vectors of data points X and Y with indices 0..m-1
386+ * along with a weight vector W of the same size.
387+ *
370388 * The output is a vector B with indices 0..n that describes a polynomial
371- * that fits the data, such the sum of abs(Y[i] - (B[0] + B[1] X[i] + B[2] X[i]^2 ... B[n] X[i]^n))
372- * for all i between 0 and m-1 is minimized.
389+ * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
390+ * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
391+ *
392+ * Accordingly, the weight vector W should be initialized by the caller with the
393+ * reciprocal square root of the variance of the error in each input data point.
394+ * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
395+ * The weights express the relative importance of each data point. If the weights are
396+ * all 1, then the data points are considered to be of equal importance when fitting
397+ * the polynomial. It is a good idea to choose weights that diminish the importance
398+ * of data points that may have higher than usual error margins.
399+ *
400+ * Errors among data points are assumed to be independent. W is represented here
401+ * as a vector although in the literature it is typically taken to be a diagonal matrix.
373402 *
374403 * That is to say, the function that generated the input data can be approximated
375404 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
@@ -379,14 +408,15 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet3
379408 * indicates perfect correspondence.
380409 *
381410 * This function first expands the X vector to a m by n matrix A such that
382- * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n.
411+ * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
412+ * multiplies it by w[i]./
383413 *
384414 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
385415 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
386416 * part is all zeroes), we can simplify the decomposition into an m by n matrix
387417 * Q1 and a n by n matrix R1 such that A = Q1 R1.
388418 *
389- * Finally we solve the system of linear equations given by R1 B = (Qtranspose Y)
419+ * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
390420 * to find B.
391421 *
392422 * For efficiency, we lay out A and Q column-wise in memory because we frequently
@@ -395,17 +425,18 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet3
395425 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
396426 * http://en.wikipedia.org/wiki/Gram-Schmidt
397427 */
398- static bool solveLeastSquares (const float * x, const float * y, uint32_t m, uint32_t n,
399- float * outB, float * outDet) {
428+ static bool solveLeastSquares (const float * x, const float * y,
429+ const float * w, uint32_t m, uint32_t n, float * outB, float * outDet) {
400430#if DEBUG_STRATEGY
401- ALOGD (" solveLeastSquares: m=%d, n=%d, x=%s, y=%s" , int (m), int (n),
402- vectorToString (x, m).string (), vectorToString (y, m).string ());
431+ ALOGD (" solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s" , int (m), int (n),
432+ vectorToString (x, m).string (), vectorToString (y, m).string (),
433+ vectorToString (w, m).string ());
403434#endif
404435
405- // Expand the X vector to a matrix A.
436+ // Expand the X vector to a matrix A, pre-multiplied by the weights .
406437 float a[n][m]; // column-major order
407438 for (uint32_t h = 0 ; h < m; h++) {
408- a[0 ][h] = 1 ;
439+ a[0 ][h] = w[h] ;
409440 for (uint32_t i = 1 ; i < n; i++) {
410441 a[i][h] = a[i - 1 ][h] * x[h];
411442 }
@@ -462,10 +493,14 @@ static bool solveLeastSquares(const float* x, const float* y, uint32_t m, uint32
462493 ALOGD (" - qr=%s" , matrixToString (&qr[0 ][0 ], m, n, false /* rowMajor*/ ).string ());
463494#endif
464495
465- // Solve R B = Qt Y to find B. This is easy because R is upper triangular.
496+ // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
466497 // We just work from bottom-right to top-left calculating B's coefficients.
498+ float wy[m];
499+ for (uint32_t h = 0 ; h < m; h++) {
500+ wy[h] = y[h] * w[h];
501+ }
467502 for (uint32_t i = n; i-- != 0 ; ) {
468- outB[i] = vectorDot (&q[i][0 ], y , m);
503+ outB[i] = vectorDot (&q[i][0 ], wy , m);
469504 for (uint32_t j = n - 1 ; j > i; j--) {
470505 outB[i] -= r[i][j] * outB[j];
471506 }
@@ -476,8 +511,9 @@ static bool solveLeastSquares(const float* x, const float* y, uint32_t m, uint32
476511#endif
477512
478513 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
479- // SSerr is the residual sum of squares (squared variance of the error),
480- // and SStot is the total sum of squares (squared variance of the data).
514+ // SSerr is the residual sum of squares (variance of the error),
515+ // and SStot is the total sum of squares (variance of the data) where each
516+ // has been weighted.
481517 float ymean = 0 ;
482518 for (uint32_t h = 0 ; h < m; h++) {
483519 ymean += y[h];
@@ -493,9 +529,9 @@ static bool solveLeastSquares(const float* x, const float* y, uint32_t m, uint32
493529 term *= x[h];
494530 err -= term * outB[i];
495531 }
496- sserr += err * err;
532+ sserr += w[h] * w[h] * err * err;
497533 float var = y[h] - ymean;
498- sstot += var * var;
534+ sstot += w[h] * w[h] * var * var;
499535 }
500536 *outDet = sstot > 0 .000001f ? 1 .0f - (sserr / sstot) : 1 ;
501537#if DEBUG_STRATEGY
@@ -513,6 +549,7 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
513549 // Iterate over movement samples in reverse time order and collect samples.
514550 float x[HISTORY_SIZE];
515551 float y[HISTORY_SIZE];
552+ float w[HISTORY_SIZE];
516553 float time[HISTORY_SIZE];
517554 uint32_t m = 0 ;
518555 uint32_t index = mIndex ;
@@ -531,6 +568,7 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
531568 const VelocityTracker::Position& position = movement.getPosition (id);
532569 x[m] = position.x ;
533570 y[m] = position.y ;
571+ w[m] = chooseWeight (index);
534572 time[m] = -age * 0 .000000001f ;
535573 index = (index == 0 ? HISTORY_SIZE : index) - 1 ;
536574 } while (++m < HISTORY_SIZE);
@@ -547,8 +585,8 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
547585 if (degree >= 1 ) {
548586 float xdet, ydet;
549587 uint32_t n = degree + 1 ;
550- if (solveLeastSquares (time, x, m, n, outEstimator->xCoeff , &xdet)
551- && solveLeastSquares (time, y, m, n, outEstimator->yCoeff , &ydet)) {
588+ if (solveLeastSquares (time, x, w, m, n, outEstimator->xCoeff , &xdet)
589+ && solveLeastSquares (time, y, w, m, n, outEstimator->yCoeff , &ydet)) {
552590 outEstimator->time = newestMovement.eventTime ;
553591 outEstimator->degree = degree;
554592 outEstimator->confidence = xdet * ydet;
@@ -572,6 +610,73 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
572610 return true ;
573611}
574612
613+ float LeastSquaresVelocityTrackerStrategy::chooseWeight (uint32_t index) const {
614+ switch (mWeighting ) {
615+ case WEIGHTING_DELTA: {
616+ // Weight points based on how much time elapsed between them and the next
617+ // point so that points that "cover" a shorter time span are weighed less.
618+ // delta 0ms: 0.5
619+ // delta 10ms: 1.0
620+ if (index == mIndex ) {
621+ return 1 .0f ;
622+ }
623+ uint32_t nextIndex = (index + 1 ) % HISTORY_SIZE;
624+ float deltaMillis = (mMovements [nextIndex].eventTime - mMovements [index].eventTime )
625+ * 0 .000001f ;
626+ if (deltaMillis < 0 ) {
627+ return 0 .5f ;
628+ }
629+ if (deltaMillis < 10 ) {
630+ return 0 .5f + deltaMillis * 0.05 ;
631+ }
632+ return 1 .0f ;
633+ }
634+
635+ case WEIGHTING_CENTRAL: {
636+ // Weight points based on their age, weighing very recent and very old points less.
637+ // age 0ms: 0.5
638+ // age 10ms: 1.0
639+ // age 50ms: 1.0
640+ // age 60ms: 0.5
641+ float ageMillis = (mMovements [mIndex ].eventTime - mMovements [index].eventTime )
642+ * 0 .000001f ;
643+ if (ageMillis < 0 ) {
644+ return 0 .5f ;
645+ }
646+ if (ageMillis < 10 ) {
647+ return 0 .5f + ageMillis * 0.05 ;
648+ }
649+ if (ageMillis < 50 ) {
650+ return 1 .0f ;
651+ }
652+ if (ageMillis < 60 ) {
653+ return 0 .5f + (60 - ageMillis) * 0.05 ;
654+ }
655+ return 0 .5f ;
656+ }
657+
658+ case WEIGHTING_RECENT: {
659+ // Weight points based on their age, weighing older points less.
660+ // age 0ms: 1.0
661+ // age 50ms: 1.0
662+ // age 100ms: 0.5
663+ float ageMillis = (mMovements [mIndex ].eventTime - mMovements [index].eventTime )
664+ * 0 .000001f ;
665+ if (ageMillis < 50 ) {
666+ return 1 .0f ;
667+ }
668+ if (ageMillis < 100 ) {
669+ return 0 .5f + (100 - ageMillis) * 0 .01f ;
670+ }
671+ return 0 .5f ;
672+ }
673+
674+ case WEIGHTING_NONE:
675+ default :
676+ return 1 .0f ;
677+ }
678+ }
679+
575680
576681// --- IntegratingVelocityTrackerStrategy ---
577682
0 commit comments