UserHistoryForgettingCurveUtils.java revision c88f61215c5b9ca6e0cc3f776e3b7da19eec9cae
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import android.text.format.DateUtils;
20import android.util.Log;
21
22public class UserHistoryForgettingCurveUtils {
23    private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
24    private static final boolean DEBUG = false;
25    private static final int FC_FREQ_MAX = 127;
26    /* package */ static final int COUNT_MAX = 3;
27    private static final int FC_LEVEL_MAX = 3;
28    /* package */ static final int ELAPSED_TIME_MAX = 15;
29    private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
30    private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
31            * DateUtils.HOUR_IN_MILLIS;
32    private static final int HALF_LIFE_HOURS = 48;
33
34    private UserHistoryForgettingCurveUtils() {
35        // This utility class is not publicly instantiable.
36    }
37
38    public static class ForgettingCurveParams {
39        private byte mFc;
40        long mLastTouchedTime = 0;
41        private final boolean mIsValid;
42
43        private void updateLastTouchedTime() {
44            mLastTouchedTime = System.currentTimeMillis();
45        }
46
47        public ForgettingCurveParams(boolean isValid) {
48            this(System.currentTimeMillis(), isValid);
49        }
50
51        private ForgettingCurveParams(long now, boolean isValid) {
52            this((int)pushCount((byte)0, isValid), now, now, isValid);
53        }
54
55        /** This constructor is called when the user history bigram dictionary is being restored. */
56        public ForgettingCurveParams(int fc, long now, long last) {
57            // All words with level >= 1 had been saved.
58            // Invalid words with level == 0 had been saved.
59            // Valid words words with level == 0 had *not* been saved.
60            this(fc, now, last, fcToLevel((byte)fc) > 0);
61        }
62
63        private ForgettingCurveParams(int fc, long now, long last, boolean isValid) {
64            mIsValid = isValid;
65            mFc = (byte)fc;
66            mLastTouchedTime = last;
67            updateElapsedTime(now);
68        }
69
70        public boolean isValid() {
71            return mIsValid;
72        }
73
74        public byte getFc() {
75            updateElapsedTime(System.currentTimeMillis());
76            return mFc;
77        }
78
79        public int getFrequency() {
80            updateElapsedTime(System.currentTimeMillis());
81            return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
82        }
83
84        public int notifyTypedAgainAndGetFrequency() {
85            updateLastTouchedTime();
86            // TODO: Check whether this word is valid or not
87            mFc = pushCount(mFc, false);
88            return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
89        }
90
91        private void updateElapsedTime(long now) {
92            final int elapsedTimeCount =
93                    (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS);
94            if (elapsedTimeCount <= 0) {
95                return;
96            }
97            for (int i = 0; i < elapsedTimeCount; ++i) {
98                mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS;
99                mFc = pushElapsedTime(mFc);
100            }
101        }
102    }
103
104    /* package */ static  int fcToElapsedTime(byte fc) {
105        return fc & 0x0F;
106    }
107
108    /* package */ static int fcToCount(byte fc) {
109        return (fc >> 4) & 0x03;
110    }
111
112    /* package */ static int fcToLevel(byte fc) {
113        return (fc >> 6) & 0x03;
114    }
115
116    private static int calcFreq(int elapsedTime, int count, int level) {
117        if (level <= 0) {
118            // Reserved words, just return -1
119            return -1;
120        }
121        if (count == COUNT_MAX) {
122            // Temporary promote because it's frequently typed recently
123            ++level;
124        }
125        final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
126        final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
127        return MathUtils.SCORE_TABLE[l - 1][et];
128    }
129
130    /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) {
131        final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
132        final int c = Math.min(COUNT_MAX, Math.max(0, count));
133        final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
134        return (byte)(et | (c << 4) | (l << 6));
135    }
136
137    public static int fcToFreq(byte fc) {
138        final int elapsedTime = fcToElapsedTime(fc);
139        final int count = fcToCount(fc);
140        final int level = fcToLevel(fc);
141        return calcFreq(elapsedTime, count, level);
142    }
143
144    public static byte pushElapsedTime(byte fc) {
145        int elapsedTime = fcToElapsedTime(fc);
146        int count = fcToCount(fc);
147        int level = fcToLevel(fc);
148        if (elapsedTime >= ELAPSED_TIME_MAX) {
149            // Downgrade level
150            elapsedTime = 0;
151            count = COUNT_MAX;
152            --level;
153        } else {
154            ++elapsedTime;
155        }
156        return calcFc(elapsedTime, count, level);
157    }
158
159    public static byte pushCount(byte fc, boolean isValid) {
160        final int elapsedTime = fcToElapsedTime(fc);
161        int count = fcToCount(fc);
162        int level = fcToLevel(fc);
163        if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) {
164            // Upgrade level
165            ++level;
166            count = 0;
167            if (DEBUG) {
168                Log.d(TAG, "Upgrade level.");
169            }
170        } else {
171            ++count;
172        }
173        return calcFc(0, count, level);
174    }
175
176    // TODO: isValid should be false for a word whose frequency is 0,
177    // or that is not in the dictionary.
178    /**
179     * Check wheather we should save the bigram to the SQL DB or not
180     */
181    public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) {
182        int level = fcToLevel(fc);
183        if (level == 0) {
184            if (isValid || !addLevel0Bigram) {
185                return false;
186            }
187        }
188        final int elapsedTime = fcToElapsedTime(fc);
189        return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
190    }
191
192    private static class MathUtils {
193        public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
194        static {
195            for (int i = 0; i < FC_LEVEL_MAX; ++i) {
196                final double initialFreq;
197                if (i >= 2) {
198                    initialFreq = (double)FC_FREQ_MAX;
199                } else if (i == 1) {
200                    initialFreq = (double)FC_FREQ_MAX / 2;
201                } else if (i == 0) {
202                    initialFreq = (double)FC_FREQ_MAX / 4;
203                } else {
204                    continue;
205                }
206                for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
207                    final double elapsedHour = j * ELAPSED_TIME_INTERVAL_HOURS;
208                    final double freq =
209                            initialFreq * Math.pow(initialFreq, elapsedHour / HALF_LIFE_HOURS);
210                    final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
211                    SCORE_TABLE[i][j] = intFreq;
212                }
213            }
214        }
215    }
216}
217