/* ** ** Copyright 2014, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "ClockEstimator" #include #include #include #include namespace android { WindowedLinearFitEstimator::WindowedLinearFitEstimator( size_t headLength, double headFactor, size_t mainLength, double tailFactor) : mHeadFactorInv(1. / headFactor), mTailFactor(tailFactor), mHistoryLength(mainLength + headLength), mHeadLength(headLength) { reset(); mXHistory.resize(mHistoryLength); mYHistory.resize(mHistoryLength); mFirstWeight = pow(headFactor, mHeadLength); } WindowedLinearFitEstimator::LinearFit::LinearFit() { reset(); } void WindowedLinearFitEstimator::LinearFit::reset() { mX = mXX = mY = mYY = mXY = mW = 0.; } double WindowedLinearFitEstimator::LinearFit::size() const { double s = mW * mW + mX * mX + mY * mY + mXX * mXX + mXY * mXY + mYY * mYY; if (s > 1e72) { // 1e72 corresponds to clock monotonic time of about 8 years ALOGW("estimator is overflowing: w=%g x=%g y=%g xx=%g xy=%g yy=%g", mW, mX, mY, mXX, mXY, mYY); } return s; } void WindowedLinearFitEstimator::LinearFit::add(double x, double y, double w) { mW += w; mX += w * x; mY += w * y; mXX += w * x * x; mXY += w * x * y; mYY += w * y * y; } void WindowedLinearFitEstimator::LinearFit::combine(const LinearFit &lf) { mW += lf.mW; mX += lf.mX; mY += lf.mY; mXX += lf.mXX; mXY += lf.mXY; mYY += lf.mYY; } void WindowedLinearFitEstimator::LinearFit::scale(double w) { mW *= w; mX *= w; mY *= w; mXX *= w; mXY *= w; mYY *= w; } double WindowedLinearFitEstimator::LinearFit::interpolate(double x) { double div = mW * mXX - mX * mX; if (fabs(div) < 1e-5 * mW * mW) { // this only should happen on the first value return x; // assuming a = 1, we could also return x + (mY - mX) / mW; } double a_div = (mW * mXY - mX * mY); double b_div = (mXX * mY - mX * mXY); ALOGV("a=%.4g b=%.4g in=%g out=%g", a_div / div, b_div / div, x, (a_div * x + b_div) / div); return (a_div * x + b_div) / div; } double WindowedLinearFitEstimator::estimate(double x, double y) { /* * TODO: We could update the head by adding the new sample to it * and amplifying it, but this approach can lead to unbounded * error. Instead, we recalculate the head at each step, which * is computationally more expensive. We could balance the two * methods by recalculating just before the error becomes * significant. */ const bool update_head = false; if (update_head) { // add new sample to the head mHead.scale(mHeadFactorInv); // amplify head mHead.add(x, y, mFirstWeight); } /* * TRICKY: place elements into the circular buffer at decreasing * indices, so that we can access past elements by addition * (thereby avoiding potentially negative indices.) */ if (mNumSamples >= mHeadLength) { // move last head sample from head to the main window size_t lastHeadIx = (mSampleIx + mHeadLength) % mHistoryLength; if (update_head) { mHead.add(mXHistory[lastHeadIx], mYHistory[lastHeadIx], -1.); // remove } mMain.add(mXHistory[lastHeadIx], mYHistory[lastHeadIx], 1.); if (mNumSamples >= mHistoryLength) { // move last main sample from main window to tail mMain.add(mXHistory[mSampleIx], mYHistory[mSampleIx], -1.); // remove mTail.add(mXHistory[mSampleIx], mYHistory[mSampleIx], 1.); mTail.scale(mTailFactor); // attenuate tail } } mXHistory.editItemAt(mSampleIx) = x; mYHistory.editItemAt(mSampleIx) = y; if (mNumSamples < mHistoryLength) { ++mNumSamples; } // recalculate head unless we were using the update method if (!update_head) { mHead.reset(); double w = mFirstWeight; for (size_t headIx = 0; headIx < mHeadLength && headIx < mNumSamples; ++headIx) { size_t ix = (mSampleIx + headIx) % mHistoryLength; mHead.add(mXHistory[ix], mYHistory[ix], w); w *= mHeadFactorInv; } } if (mSampleIx > 0) { --mSampleIx; } else { mSampleIx = mHistoryLength - 1; } // return estimation result LinearFit total; total.combine(mHead); total.combine(mMain); total.combine(mTail); return total.interpolate(x); } void WindowedLinearFitEstimator::reset() { mHead.reset(); mMain.reset(); mTail.reset(); mNumSamples = 0; mSampleIx = mHistoryLength - 1; } }; // namespace android