1 /*
2  * Copyright (C) 2013 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 
17 #include "suggest/core/layout/proximity_info_state_utils.h"
18 
19 #include <algorithm>
20 #include <cmath>
21 #include <cstring> // for memset()
22 #include <sstream> // for debug prints
23 #include <unordered_map>
24 #include <vector>
25 
26 #include "defines.h"
27 #include "suggest/core/layout/geometry_utils.h"
28 #include "suggest/core/layout/normal_distribution_2d.h"
29 #include "suggest/core/layout/proximity_info.h"
30 #include "suggest/core/layout/proximity_info_params.h"
31 
32 namespace latinime {
33 
trimLastTwoTouchPoints(std::vector<int> * sampledInputXs,std::vector<int> * sampledInputYs,std::vector<int> * sampledInputTimes,std::vector<int> * sampledLengthCache,std::vector<int> * sampledInputIndice)34 /* static */ int ProximityInfoStateUtils::trimLastTwoTouchPoints(std::vector<int> *sampledInputXs,
35         std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
36         std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
37     const int nextStartIndex = (*sampledInputIndice)[sampledInputIndice->size() - 2];
38     popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
39             sampledInputIndice);
40     popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
41             sampledInputIndice);
42     return nextStartIndex;
43 }
44 
updateTouchPoints(const ProximityInfo * const proximityInfo,const int maxPointToKeyLength,const int * const inputProximities,const int * const inputXCoordinates,const int * const inputYCoordinates,const int * const times,const int * const pointerIds,const int inputSize,const bool isGeometric,const int pointerId,const int pushTouchPointStartIndex,std::vector<int> * sampledInputXs,std::vector<int> * sampledInputYs,std::vector<int> * sampledInputTimes,std::vector<int> * sampledLengthCache,std::vector<int> * sampledInputIndice)45 /* static */ int ProximityInfoStateUtils::updateTouchPoints(
46         const ProximityInfo *const proximityInfo, const int maxPointToKeyLength,
47         const int *const inputProximities, const int *const inputXCoordinates,
48         const int *const inputYCoordinates, const int *const times, const int *const pointerIds,
49         const int inputSize, const bool isGeometric, const int pointerId,
50         const int pushTouchPointStartIndex, std::vector<int> *sampledInputXs,
51         std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
52         std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
53     if (DEBUG_SAMPLING_POINTS) {
54         if (times) {
55             for (int i = 0; i < inputSize; ++i) {
56                 AKLOGI("(%d) x %d, y %d, time %d",
57                         i, inputXCoordinates[i], inputYCoordinates[i], times[i]);
58             }
59         }
60     }
61 #ifdef DO_ASSERT_TEST
62     if (times) {
63         for (int i = 0; i < inputSize; ++i) {
64             if (i > 0) {
65                 if (times[i] < times[i - 1]) {
66                     AKLOGI("Invalid time sequence. %d, %d", times[i - 1], times[i]);
67                     ASSERT(false);
68                 }
69             }
70         }
71     }
72 #endif
73     const bool proximityOnly = !isGeometric
74             && (inputXCoordinates[0] < 0 || inputYCoordinates[0] < 0);
75     int lastInputIndex = pushTouchPointStartIndex;
76     for (int i = lastInputIndex; i < inputSize; ++i) {
77         const int pid = pointerIds ? pointerIds[i] : 0;
78         if (pointerId == pid) {
79             lastInputIndex = i;
80         }
81     }
82     if (DEBUG_GEO_FULL) {
83         AKLOGI("Init ProximityInfoState: last input index = %d", lastInputIndex);
84     }
85     // Working space to save near keys distances for current, prev and prevprev input point.
86     NearKeysDistanceMap nearKeysDistances[3];
87     // These pointers are swapped for each inputs points.
88     NearKeysDistanceMap *currentNearKeysDistances = &nearKeysDistances[0];
89     NearKeysDistanceMap *prevNearKeysDistances = &nearKeysDistances[1];
90     NearKeysDistanceMap *prevPrevNearKeysDistances = &nearKeysDistances[2];
91     // "sumAngle" is accumulated by each angle of input points. And when "sumAngle" exceeds
92     // the threshold we save that point, reset sumAngle. This aims to keep the figure of
93     // the curve.
94     float sumAngle = 0.0f;
95 
96     for (int i = pushTouchPointStartIndex; i <= lastInputIndex; ++i) {
97         // Assuming pointerId == 0 if pointerIds is null.
98         const int pid = pointerIds ? pointerIds[i] : 0;
99         if (DEBUG_GEO_FULL) {
100             AKLOGI("Init ProximityInfoState: (%d)PID = %d", i, pid);
101         }
102         if (pointerId == pid) {
103             const int c = isGeometric ?
104                     NOT_A_COORDINATE : getPrimaryCodePointAt(inputProximities, i);
105             const int x = proximityOnly ? NOT_A_COORDINATE : inputXCoordinates[i];
106             const int y = proximityOnly ? NOT_A_COORDINATE : inputYCoordinates[i];
107             const int time = times ? times[i] : -1;
108 
109             if (i > 1) {
110                 const float prevAngle = GeometryUtils::getAngle(
111                         inputXCoordinates[i - 2], inputYCoordinates[i - 2],
112                         inputXCoordinates[i - 1], inputYCoordinates[i - 1]);
113                 const float currentAngle = GeometryUtils::getAngle(
114                         inputXCoordinates[i - 1], inputYCoordinates[i - 1], x, y);
115                 sumAngle += GeometryUtils::getAngleDiff(prevAngle, currentAngle);
116             }
117 
118             if (pushTouchPoint(proximityInfo, maxPointToKeyLength, i, c, x, y, time,
119                     isGeometric, isGeometric /* doSampling */, i == lastInputIndex,
120                     sumAngle, currentNearKeysDistances, prevNearKeysDistances,
121                     prevPrevNearKeysDistances, sampledInputXs, sampledInputYs, sampledInputTimes,
122                     sampledLengthCache, sampledInputIndice)) {
123                 // Previous point information was popped.
124                 NearKeysDistanceMap *tmp = prevNearKeysDistances;
125                 prevNearKeysDistances = currentNearKeysDistances;
126                 currentNearKeysDistances = tmp;
127             } else {
128                 NearKeysDistanceMap *tmp = prevPrevNearKeysDistances;
129                 prevPrevNearKeysDistances = prevNearKeysDistances;
130                 prevNearKeysDistances = currentNearKeysDistances;
131                 currentNearKeysDistances = tmp;
132                 sumAngle = 0.0f;
133             }
134         }
135     }
136     return sampledInputXs->size();
137 }
138 
getProximityCodePointsAt(const int * const inputProximities,const int index)139 /* static */ const int *ProximityInfoStateUtils::getProximityCodePointsAt(
140         const int *const inputProximities, const int index) {
141     return inputProximities + (index * MAX_PROXIMITY_CHARS_SIZE);
142 }
143 
getPrimaryCodePointAt(const int * const inputProximities,const int index)144 /* static */ int ProximityInfoStateUtils::getPrimaryCodePointAt(const int *const inputProximities,
145         const int index) {
146     return getProximityCodePointsAt(inputProximities, index)[0];
147 }
148 
initPrimaryInputWord(const int inputSize,const int * const inputProximities,int * primaryInputWord)149 /* static */ void ProximityInfoStateUtils::initPrimaryInputWord(const int inputSize,
150         const int *const inputProximities, int *primaryInputWord) {
151     memset(primaryInputWord, 0, sizeof(primaryInputWord[0]) * MAX_WORD_LENGTH);
152     for (int i = 0; i < inputSize; ++i) {
153         primaryInputWord[i] = getPrimaryCodePointAt(inputProximities, i);
154     }
155 }
156 
calculateSquaredDistanceFromSweetSpotCenter(const ProximityInfo * const proximityInfo,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int keyIndex,const int inputIndex)157 /* static */ float ProximityInfoStateUtils::calculateSquaredDistanceFromSweetSpotCenter(
158         const ProximityInfo *const proximityInfo, const std::vector<int> *const sampledInputXs,
159         const std::vector<int> *const sampledInputYs, const int keyIndex, const int inputIndex) {
160     const float sweetSpotCenterX = proximityInfo->getSweetSpotCenterXAt(keyIndex);
161     const float sweetSpotCenterY = proximityInfo->getSweetSpotCenterYAt(keyIndex);
162     const float inputX = static_cast<float>((*sampledInputXs)[inputIndex]);
163     const float inputY = static_cast<float>((*sampledInputYs)[inputIndex]);
164     return GeometryUtils::SQUARE_FLOAT(inputX - sweetSpotCenterX)
165             + GeometryUtils::SQUARE_FLOAT(inputY - sweetSpotCenterY);
166 }
167 
calculateNormalizedSquaredDistance(const ProximityInfo * const proximityInfo,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int keyIndex,const int inputIndex)168 /* static */ float ProximityInfoStateUtils::calculateNormalizedSquaredDistance(
169         const ProximityInfo *const proximityInfo, const std::vector<int> *const sampledInputXs,
170         const std::vector<int> *const sampledInputYs, const int keyIndex, const int inputIndex) {
171     if (keyIndex == NOT_AN_INDEX) {
172         return ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
173     }
174     if (!proximityInfo->hasSweetSpotData(keyIndex)) {
175         return ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
176     }
177     if (NOT_A_COORDINATE == (*sampledInputXs)[inputIndex]) {
178         return ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
179     }
180     const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(proximityInfo,
181             sampledInputXs, sampledInputYs, keyIndex, inputIndex);
182     const float squaredRadius = GeometryUtils::SQUARE_FLOAT(
183             proximityInfo->getSweetSpotRadiiAt(keyIndex));
184     return squaredDistance / squaredRadius;
185 }
186 
initGeometricDistanceInfos(const ProximityInfo * const proximityInfo,const int sampledInputSize,const int lastSavedInputSize,const bool isGeometric,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,std::vector<float> * sampledNormalizedSquaredLengthCache)187 /* static */ void ProximityInfoStateUtils::initGeometricDistanceInfos(
188         const ProximityInfo *const proximityInfo, const int sampledInputSize,
189         const int lastSavedInputSize, const bool isGeometric,
190         const std::vector<int> *const sampledInputXs,
191         const std::vector<int> *const sampledInputYs,
192         std::vector<float> *sampledNormalizedSquaredLengthCache) {
193     const int keyCount = proximityInfo->getKeyCount();
194     sampledNormalizedSquaredLengthCache->resize(sampledInputSize * keyCount);
195     for (int i = lastSavedInputSize; i < sampledInputSize; ++i) {
196         for (int k = 0; k < keyCount; ++k) {
197             const int index = i * keyCount + k;
198             const int x = (*sampledInputXs)[i];
199             const int y = (*sampledInputYs)[i];
200             const float normalizedSquaredDistance =
201                     proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(
202                             k, x, y, isGeometric);
203             (*sampledNormalizedSquaredLengthCache)[index] = normalizedSquaredDistance;
204         }
205     }
206 }
207 
popInputData(std::vector<int> * sampledInputXs,std::vector<int> * sampledInputYs,std::vector<int> * sampledInputTimes,std::vector<int> * sampledLengthCache,std::vector<int> * sampledInputIndice)208 /* static */ void ProximityInfoStateUtils::popInputData(std::vector<int> *sampledInputXs,
209         std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
210         std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
211     sampledInputXs->pop_back();
212     sampledInputYs->pop_back();
213     sampledInputTimes->pop_back();
214     sampledLengthCache->pop_back();
215     sampledInputIndice->pop_back();
216 }
217 
refreshSpeedRates(const int inputSize,const int * const xCoordinates,const int * const yCoordinates,const int * const times,const int lastSavedInputSize,const int sampledInputSize,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const std::vector<int> * const sampledInputTimes,const std::vector<int> * const sampledLengthCache,const std::vector<int> * const sampledInputIndice,std::vector<float> * sampledSpeedRates,std::vector<float> * sampledDirections)218 /* static */ float ProximityInfoStateUtils::refreshSpeedRates(const int inputSize,
219         const int *const xCoordinates, const int *const yCoordinates, const int *const times,
220         const int lastSavedInputSize, const int sampledInputSize,
221         const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs,
222         const std::vector<int> *const sampledInputTimes,
223         const std::vector<int> *const sampledLengthCache,
224         const std::vector<int> *const sampledInputIndice, std::vector<float> *sampledSpeedRates,
225         std::vector<float> *sampledDirections) {
226     // Relative speed calculation.
227     const int sumDuration = sampledInputTimes->back() - sampledInputTimes->front();
228     const int sumLength = sampledLengthCache->back() - sampledLengthCache->front();
229     const float averageSpeed = static_cast<float>(sumLength) / static_cast<float>(sumDuration);
230     sampledSpeedRates->resize(sampledInputSize);
231     for (int i = lastSavedInputSize; i < sampledInputSize; ++i) {
232         const int index = (*sampledInputIndice)[i];
233         int length = 0;
234         int duration = 0;
235 
236         // Calculate velocity by using distances and durations of
237         // ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION points for both forward and
238         // backward.
239         const int forwardNumPoints = std::min(inputSize - 1,
240                 index + ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
241         for (int j = index; j < forwardNumPoints; ++j) {
242             if (i < sampledInputSize - 1 && j >= (*sampledInputIndice)[i + 1]) {
243                 break;
244             }
245             length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
246                     xCoordinates[j + 1], yCoordinates[j + 1]);
247             duration += times[j + 1] - times[j];
248         }
249         const int backwardNumPoints = std::max(0,
250                 index - ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
251         for (int j = index - 1; j >= backwardNumPoints; --j) {
252             if (i > 0 && j < (*sampledInputIndice)[i - 1]) {
253                 break;
254             }
255             // TODO: use mSampledLengthCache instead?
256             length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
257                     xCoordinates[j + 1], yCoordinates[j + 1]);
258             duration += times[j + 1] - times[j];
259         }
260         if (duration == 0 || sumDuration == 0) {
261             // Cannot calculate speed; thus, it gives an average value (1.0);
262             (*sampledSpeedRates)[i] = 1.0f;
263         } else {
264             const float speed = static_cast<float>(length) / static_cast<float>(duration);
265             (*sampledSpeedRates)[i] = speed / averageSpeed;
266         }
267     }
268 
269     // Direction calculation.
270     sampledDirections->resize(sampledInputSize - 1);
271     for (int i = std::max(0, lastSavedInputSize - 1); i < sampledInputSize - 1; ++i) {
272         (*sampledDirections)[i] = getDirection(sampledInputXs, sampledInputYs, i, i + 1);
273     }
274     return averageSpeed;
275 }
276 
refreshBeelineSpeedRates(const int mostCommonKeyWidth,const float averageSpeed,const int inputSize,const int * const xCoordinates,const int * const yCoordinates,const int * times,const int sampledInputSize,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const std::vector<int> * const inputIndice,std::vector<int> * beelineSpeedPercentiles)277 /* static */ void ProximityInfoStateUtils::refreshBeelineSpeedRates(const int mostCommonKeyWidth,
278         const float averageSpeed, const int inputSize, const int *const xCoordinates,
279         const int *const yCoordinates, const int *times, const int sampledInputSize,
280         const std::vector<int> *const sampledInputXs,
281         const std::vector<int> *const sampledInputYs, const std::vector<int> *const inputIndice,
282         std::vector<int> *beelineSpeedPercentiles) {
283     if (DEBUG_SAMPLING_POINTS) {
284         AKLOGI("--- refresh beeline speed rates");
285     }
286     beelineSpeedPercentiles->resize(sampledInputSize);
287     for (int i = 0; i < sampledInputSize; ++i) {
288         (*beelineSpeedPercentiles)[i] = static_cast<int>(calculateBeelineSpeedRate(
289                 mostCommonKeyWidth, averageSpeed, i, inputSize, xCoordinates, yCoordinates, times,
290                 sampledInputSize, sampledInputXs, sampledInputYs, inputIndice) * MAX_PERCENTILE);
291     }
292 }
293 
getDirection(const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int index0,const int index1)294 /* static */float ProximityInfoStateUtils::getDirection(
295         const std::vector<int> *const sampledInputXs,
296         const std::vector<int> *const sampledInputYs, const int index0, const int index1) {
297     ASSERT(sampledInputXs && sampledInputYs);
298     const int sampledInputSize =sampledInputXs->size();
299     if (index0 < 0 || index0 > sampledInputSize - 1) {
300         return 0.0f;
301     }
302     if (index1 < 0 || index1 > sampledInputSize - 1) {
303         return 0.0f;
304     }
305     const int x1 = (*sampledInputXs)[index0];
306     const int y1 = (*sampledInputYs)[index0];
307     const int x2 = (*sampledInputXs)[index1];
308     const int y2 = (*sampledInputYs)[index1];
309     return GeometryUtils::getAngle(x1, y1, x2, y2);
310 }
311 
312 // Calculating point to key distance for all near keys and returning the distance between
313 // the given point and the nearest key position.
updateNearKeysDistances(const ProximityInfo * const proximityInfo,const float maxPointToKeyLength,const int x,const int y,const bool isGeometric,NearKeysDistanceMap * const currentNearKeysDistances)314 /* static */ float ProximityInfoStateUtils::updateNearKeysDistances(
315         const ProximityInfo *const proximityInfo, const float maxPointToKeyLength, const int x,
316         const int y, const bool isGeometric, NearKeysDistanceMap *const currentNearKeysDistances) {
317     currentNearKeysDistances->clear();
318     const int keyCount = proximityInfo->getKeyCount();
319     float nearestKeyDistance = maxPointToKeyLength;
320     for (int k = 0; k < keyCount; ++k) {
321         const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y,
322                 isGeometric);
323         if (dist < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE) {
324             currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
325         }
326         if (nearestKeyDistance > dist) {
327             nearestKeyDistance = dist;
328         }
329     }
330     return nearestKeyDistance;
331 }
332 
333 // Check if previous point is at local minimum position to near keys.
isPrevLocalMin(const NearKeysDistanceMap * const currentNearKeysDistances,const NearKeysDistanceMap * const prevNearKeysDistances,const NearKeysDistanceMap * const prevPrevNearKeysDistances)334 /* static */ bool ProximityInfoStateUtils::isPrevLocalMin(
335         const NearKeysDistanceMap *const currentNearKeysDistances,
336         const NearKeysDistanceMap *const prevNearKeysDistances,
337         const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
338     for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
339             it != prevNearKeysDistances->end(); ++it) {
340         NearKeysDistanceMap::const_iterator itPP = prevPrevNearKeysDistances->find(it->first);
341         NearKeysDistanceMap::const_iterator itC = currentNearKeysDistances->find(it->first);
342         const bool isPrevPrevNear = (itPP == prevPrevNearKeysDistances->end()
343                 || itPP->second > it->second + ProximityInfoParams::MARGIN_FOR_PREV_LOCAL_MIN);
344         const bool isCurrentNear = (itC == currentNearKeysDistances->end()
345                 || itC->second > it->second + ProximityInfoParams::MARGIN_FOR_PREV_LOCAL_MIN);
346         if (isPrevPrevNear && isCurrentNear) {
347             return true;
348         }
349     }
350     return false;
351 }
352 
353 // Calculating a point score that indicates usefulness of the point.
getPointScore(const int mostCommonKeyWidth,const int x,const int y,const int time,const bool lastPoint,const float nearest,const float sumAngle,const NearKeysDistanceMap * const currentNearKeysDistances,const NearKeysDistanceMap * const prevNearKeysDistances,const NearKeysDistanceMap * const prevPrevNearKeysDistances,std::vector<int> * sampledInputXs,std::vector<int> * sampledInputYs)354 /* static */ float ProximityInfoStateUtils::getPointScore(const int mostCommonKeyWidth,
355         const int x, const int y, const int time, const bool lastPoint, const float nearest,
356         const float sumAngle, const NearKeysDistanceMap *const currentNearKeysDistances,
357         const NearKeysDistanceMap *const prevNearKeysDistances,
358         const NearKeysDistanceMap *const prevPrevNearKeysDistances,
359         std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs) {
360     const size_t size = sampledInputXs->size();
361     // If there is only one point, add this point. Besides, if the previous point's distance map
362     // is empty, we re-compute nearby keys distances from the current point.
363     // Note that the current point is the first point in the incremental input that needs to
364     // be re-computed.
365     if (size <= 1 || prevNearKeysDistances->empty()) {
366         return 0.0f;
367     }
368 
369     const int baseSampleRate = mostCommonKeyWidth;
370     const int distPrev = GeometryUtils::getDistanceInt(sampledInputXs->back(),
371             sampledInputYs->back(), (*sampledInputXs)[size - 2],
372             (*sampledInputYs)[size - 2]) * ProximityInfoParams::DISTANCE_BASE_SCALE;
373     float score = 0.0f;
374 
375     // Location
376     if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances,
377         prevPrevNearKeysDistances)) {
378         score += ProximityInfoParams::NOT_LOCALMIN_DISTANCE_SCORE;
379     } else if (nearest < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_POINT_SCORE) {
380         // Promote points nearby keys
381         score += ProximityInfoParams::LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
382     }
383     // Angle
384     const float angle1 = GeometryUtils::getAngle(x, y, sampledInputXs->back(),
385             sampledInputYs->back());
386     const float angle2 = GeometryUtils::getAngle(sampledInputXs->back(), sampledInputYs->back(),
387             (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2]);
388     const float angleDiff = GeometryUtils::getAngleDiff(angle1, angle2);
389 
390     // Save corner
391     if (distPrev > baseSampleRate * ProximityInfoParams::CORNER_CHECK_DISTANCE_THRESHOLD_SCALE
392             && (sumAngle > ProximityInfoParams::CORNER_SUM_ANGLE_THRESHOLD
393                     || angleDiff > ProximityInfoParams::CORNER_ANGLE_THRESHOLD_FOR_POINT_SCORE)) {
394         score += ProximityInfoParams::CORNER_SCORE;
395     }
396     return score;
397 }
398 
399 // Sampling touch point and pushing information to vectors.
400 // Returning if previous point is popped or not.
pushTouchPoint(const ProximityInfo * const proximityInfo,const int maxPointToKeyLength,const int inputIndex,const int nodeCodePoint,int x,int y,const int time,const bool isGeometric,const bool doSampling,const bool isLastPoint,const float sumAngle,NearKeysDistanceMap * const currentNearKeysDistances,const NearKeysDistanceMap * const prevNearKeysDistances,const NearKeysDistanceMap * const prevPrevNearKeysDistances,std::vector<int> * sampledInputXs,std::vector<int> * sampledInputYs,std::vector<int> * sampledInputTimes,std::vector<int> * sampledLengthCache,std::vector<int> * sampledInputIndice)401 /* static */ bool ProximityInfoStateUtils::pushTouchPoint(const ProximityInfo *const proximityInfo,
402         const int maxPointToKeyLength, const int inputIndex, const int nodeCodePoint, int x, int y,
403         const int time, const bool isGeometric, const bool doSampling,
404         const bool isLastPoint, const float sumAngle,
405         NearKeysDistanceMap *const currentNearKeysDistances,
406         const NearKeysDistanceMap *const prevNearKeysDistances,
407         const NearKeysDistanceMap *const prevPrevNearKeysDistances,
408         std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
409         std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
410         std::vector<int> *sampledInputIndice) {
411     const int mostCommonKeyWidth = proximityInfo->getMostCommonKeyWidth();
412 
413     size_t size = sampledInputXs->size();
414     bool popped = false;
415     if (nodeCodePoint < 0 && doSampling) {
416         const float nearest = updateNearKeysDistances(proximityInfo, maxPointToKeyLength, x, y,
417                 isGeometric, currentNearKeysDistances);
418         const float score = getPointScore(mostCommonKeyWidth, x, y, time, isLastPoint, nearest,
419                 sumAngle, currentNearKeysDistances, prevNearKeysDistances,
420                 prevPrevNearKeysDistances, sampledInputXs, sampledInputYs);
421         if (score < 0) {
422             // Pop previous point because it would be useless.
423             popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
424                     sampledInputIndice);
425             size = sampledInputXs->size();
426             popped = true;
427         } else {
428             popped = false;
429         }
430         // Check if the last point should be skipped.
431         if (isLastPoint && size > 0) {
432             if (GeometryUtils::getDistanceInt(x, y, sampledInputXs->back(), sampledInputYs->back())
433                     * ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE < mostCommonKeyWidth) {
434                 // This point is not used because it's too close to the previous point.
435                 if (DEBUG_GEO_FULL) {
436                     AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
437                            "width = %d", size, x, y, sampledInputXs->back(),
438                            sampledInputYs->back(), GeometryUtils::getDistanceInt(
439                                    x, y, sampledInputXs->back(), sampledInputYs->back()),
440                            mostCommonKeyWidth
441                                    / ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE);
442                 }
443                 return popped;
444             }
445         }
446     }
447 
448     if (nodeCodePoint >= 0 && (x < 0 || y < 0)) {
449         const int keyId = proximityInfo->getKeyIndexOf(nodeCodePoint);
450         if (keyId >= 0) {
451             x = proximityInfo->getKeyCenterXOfKeyIdG(keyId, NOT_AN_INDEX, isGeometric);
452             y = proximityInfo->getKeyCenterYOfKeyIdG(keyId, NOT_AN_INDEX, isGeometric);
453         }
454     }
455 
456     // Pushing point information.
457     if (size > 0) {
458         sampledLengthCache->push_back(
459                 sampledLengthCache->back() + GeometryUtils::getDistanceInt(
460                         x, y, sampledInputXs->back(), sampledInputYs->back()));
461     } else {
462         sampledLengthCache->push_back(0);
463     }
464     sampledInputXs->push_back(x);
465     sampledInputYs->push_back(y);
466     sampledInputTimes->push_back(time);
467     sampledInputIndice->push_back(inputIndex);
468     if (DEBUG_GEO_FULL) {
469         AKLOGI("pushTouchPoint: x = %03d, y = %03d, time = %d, index = %d, popped ? %01d",
470                 x, y, time, inputIndex, popped);
471     }
472     return popped;
473 }
474 
calculateBeelineSpeedRate(const int mostCommonKeyWidth,const float averageSpeed,const int id,const int inputSize,const int * const xCoordinates,const int * const yCoordinates,const int * times,const int sampledInputSize,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const std::vector<int> * const sampledInputIndices)475 /* static */ float ProximityInfoStateUtils::calculateBeelineSpeedRate(const int mostCommonKeyWidth,
476         const float averageSpeed, const int id, const int inputSize, const int *const xCoordinates,
477         const int *const yCoordinates, const int *times, const int sampledInputSize,
478         const std::vector<int> *const sampledInputXs,
479         const std::vector<int> *const sampledInputYs,
480         const std::vector<int> *const sampledInputIndices) {
481     if (sampledInputSize <= 0 || averageSpeed < 0.001f) {
482         if (DEBUG_SAMPLING_POINTS) {
483             AKLOGI("--- invalid state: cancel. size = %d, ave = %f",
484                     sampledInputSize, averageSpeed);
485         }
486         return 1.0f;
487     }
488     const int lookupRadius = mostCommonKeyWidth
489             * ProximityInfoParams::LOOKUP_RADIUS_PERCENTILE / MAX_PERCENTILE;
490     const int x0 = (*sampledInputXs)[id];
491     const int y0 = (*sampledInputYs)[id];
492     const int actualInputIndex = (*sampledInputIndices)[id];
493     int tempTime = 0;
494     int tempBeelineDistance = 0;
495     int start = actualInputIndex;
496     // lookup forward
497     while (start > 0 && tempBeelineDistance < lookupRadius) {
498         tempTime += times[start] - times[start - 1];
499         --start;
500         tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[start],
501                 yCoordinates[start]);
502     }
503     // Exclusive unless this is an edge point
504     if (start > 0 && start < actualInputIndex) {
505         ++start;
506     }
507     tempTime= 0;
508     tempBeelineDistance = 0;
509     int end = actualInputIndex;
510     // lookup backward
511     while (end < (inputSize - 1) && tempBeelineDistance < lookupRadius) {
512         tempTime += times[end + 1] - times[end];
513         ++end;
514         tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[end],
515                 yCoordinates[end]);
516     }
517     // Exclusive unless this is an edge point
518     if (end > actualInputIndex && end < (inputSize - 1)) {
519         --end;
520     }
521 
522     if (start >= end) {
523         if (DEBUG_DOUBLE_LETTER) {
524             AKLOGI("--- double letter: start == end %d", start);
525         }
526         return 1.0f;
527     }
528 
529     const int x2 = xCoordinates[start];
530     const int y2 = yCoordinates[start];
531     const int x3 = xCoordinates[end];
532     const int y3 = yCoordinates[end];
533     const int beelineDistance = GeometryUtils::getDistanceInt(x2, y2, x3, y3);
534     int adjustedStartTime = times[start];
535     if (start == 0 && actualInputIndex == 0 && inputSize > 1) {
536         adjustedStartTime += ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
537     }
538     int adjustedEndTime = times[end];
539     if (end == (inputSize - 1) && inputSize > 1) {
540         adjustedEndTime -= ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
541     }
542     const int time = adjustedEndTime - adjustedStartTime;
543     if (time <= 0) {
544         return 1.0f;
545     }
546 
547     if (time >= ProximityInfoParams::STRONG_DOUBLE_LETTER_TIME_MILLIS){
548         return 0.0f;
549     }
550     if (DEBUG_DOUBLE_LETTER) {
551         AKLOGI("--- (%d, %d) double letter: start = %d, end = %d, dist = %d, time = %d,"
552                 " speed = %f, ave = %f, val = %f, start time = %d, end time = %d",
553                 id, (*sampledInputIndices)[id], start, end, beelineDistance, time,
554                 (static_cast<float>(beelineDistance) / static_cast<float>(time)), averageSpeed,
555                 ((static_cast<float>(beelineDistance) / static_cast<float>(time))
556                         / averageSpeed), adjustedStartTime, adjustedEndTime);
557     }
558     // Offset 1%
559     // TODO: Detect double letter more smartly
560     return 0.01f + static_cast<float>(beelineDistance) / static_cast<float>(time) / averageSpeed;
561 }
562 
getPointAngle(const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int index)563 /* static */ float ProximityInfoStateUtils::getPointAngle(
564         const std::vector<int> *const sampledInputXs,
565         const std::vector<int> *const sampledInputYs, const int index) {
566     if (!sampledInputXs || !sampledInputYs) {
567         return 0.0f;
568     }
569     const int sampledInputSize = sampledInputXs->size();
570     if (index <= 0 || index >= sampledInputSize - 1) {
571         return 0.0f;
572     }
573     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index - 1, index);
574     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index, index + 1);
575     const float directionDiff = GeometryUtils::getAngleDiff(previousDirection, nextDirection);
576     return directionDiff;
577 }
578 
getPointsAngle(const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int index0,const int index1,const int index2)579 /* static */ float ProximityInfoStateUtils::getPointsAngle(
580         const std::vector<int> *const sampledInputXs,
581         const std::vector<int> *const sampledInputYs,
582         const int index0, const int index1, const int index2) {
583     if (!sampledInputXs || !sampledInputYs) {
584         return 0.0f;
585     }
586     const int sampledInputSize = sampledInputXs->size();
587     if (index0 < 0 || index0 > sampledInputSize - 1) {
588         return 0.0f;
589     }
590     if (index1 < 0 || index1 > sampledInputSize - 1) {
591         return 0.0f;
592     }
593     if (index2 < 0 || index2 > sampledInputSize - 1) {
594         return 0.0f;
595     }
596     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index0, index1);
597     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index1, index2);
598     return GeometryUtils::getAngleDiff(previousDirection, nextDirection);
599 }
600 
601 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
602 // wrong to compare with mMaxPointToKeyLength.
getPointToKeyByIdLength(const float maxPointToKeyLength,const std::vector<float> * const sampledNormalizedSquaredLengthCache,const int keyCount,const int inputIndex,const int keyId)603 /* static */ float ProximityInfoStateUtils::getPointToKeyByIdLength(const float maxPointToKeyLength,
604         const std::vector<float> *const sampledNormalizedSquaredLengthCache, const int keyCount,
605         const int inputIndex, const int keyId) {
606     if (keyId != NOT_AN_INDEX) {
607         const int index = inputIndex * keyCount + keyId;
608         return std::min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
609     }
610     // If the char is not a key on the keyboard then return the max length.
611     return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
612 }
613 
614 // Updates probabilities of aligning to some keys and skipping.
615 // Word suggestion should be based on this probabilities.
updateAlignPointProbabilities(const float maxPointToKeyLength,const int mostCommonKeyWidth,const int keyCount,const int start,const int sampledInputSize,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const std::vector<float> * const sampledSpeedRates,const std::vector<int> * const sampledLengthCache,const std::vector<float> * const sampledNormalizedSquaredLengthCache,const ProximityInfo * const proximityInfo,std::vector<std::unordered_map<int,float>> * charProbabilities)616 /* static */ void ProximityInfoStateUtils::updateAlignPointProbabilities(
617         const float maxPointToKeyLength, const int mostCommonKeyWidth, const int keyCount,
618         const int start, const int sampledInputSize, const std::vector<int> *const sampledInputXs,
619         const std::vector<int> *const sampledInputYs,
620         const std::vector<float> *const sampledSpeedRates,
621         const std::vector<int> *const sampledLengthCache,
622         const std::vector<float> *const sampledNormalizedSquaredLengthCache,
623         const ProximityInfo *const proximityInfo,
624         std::vector<std::unordered_map<int, float>> *charProbabilities) {
625     charProbabilities->resize(sampledInputSize);
626     // Calculates probabilities of using a point as a correlated point with the character
627     // for each point.
628     for (int i = start; i < sampledInputSize; ++i) {
629         (*charProbabilities)[i].clear();
630         // First, calculates skip probability. Starts from MAX_SKIP_PROBABILITY.
631         // Note that all values that are multiplied to this probability should be in [0.0, 1.0];
632         float skipProbability = ProximityInfoParams::MAX_SKIP_PROBABILITY;
633 
634         const float currentAngle = getPointAngle(sampledInputXs, sampledInputYs, i);
635         const float speedRate = (*sampledSpeedRates)[i];
636 
637         float nearestKeyDistance = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
638         for (int j = 0; j < keyCount; ++j) {
639             const float distance = getPointToKeyByIdLength(
640                     maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j);
641             if (distance < nearestKeyDistance) {
642                 nearestKeyDistance = distance;
643             }
644         }
645 
646         if (i == 0) {
647             skipProbability *= std::min(1.0f,
648                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
649                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
650             // Promote the first point
651             skipProbability *= ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY;
652         } else if (i == sampledInputSize - 1) {
653             skipProbability *= std::min(1.0f,
654                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT_FOR_LAST
655                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS_FOR_LAST);
656             // Promote the last point
657             skipProbability *= ProximityInfoParams::SKIP_LAST_POINT_PROBABILITY;
658         } else {
659             // If the current speed is relatively slower than adjacent keys, we promote this point.
660             if ((*sampledSpeedRates)[i - 1] - ProximityInfoParams::SPEED_MARGIN > speedRate
661                     && speedRate
662                             < (*sampledSpeedRates)[i + 1] - ProximityInfoParams::SPEED_MARGIN) {
663                 if (currentAngle < ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
664                     skipProbability *= std::min(1.0f, speedRate
665                             * ProximityInfoParams::SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY);
666                 } else {
667                     // If the angle is small enough, we promote this point more. (e.g. pit vs put)
668                     skipProbability *= std::min(1.0f,
669                             speedRate * ProximityInfoParams::SPEED_WEIGHT_FOR_SKIP_PROBABILITY
670                                     + ProximityInfoParams::MIN_SPEED_RATE_FOR_SKIP_PROBABILITY);
671                 }
672             }
673 
674             skipProbability *= std::min(1.0f,
675                     speedRate * nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
676                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
677 
678             // Adjusts skip probability by a rate depending on angle.
679             // ANGLE_RATE of skipProbability is adjusted by current angle.
680             skipProbability *= (M_PI_F - currentAngle) / M_PI_F * ProximityInfoParams::ANGLE_WEIGHT
681                     + (1.0f - ProximityInfoParams::ANGLE_WEIGHT);
682             if (currentAngle > ProximityInfoParams::DEEP_CORNER_ANGLE_THRESHOLD) {
683                 skipProbability *= ProximityInfoParams::SKIP_DEEP_CORNER_PROBABILITY;
684             }
685             // We assume the angle of this point is the angle for point[i], point[i - 2]
686             // and point[i - 3]. The reason why we don't use the angle for point[i], point[i - 1]
687             // and point[i - 2] is this angle can be more affected by the noise.
688             const float prevAngle = getPointsAngle(sampledInputXs, sampledInputYs, i, i - 2, i - 3);
689             if (i >= 3 && prevAngle < ProximityInfoParams::STRAIGHT_ANGLE_THRESHOLD
690                     && currentAngle > ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
691                 skipProbability *= ProximityInfoParams::SKIP_CORNER_PROBABILITY;
692             }
693         }
694 
695         // probabilities must be in [0.0, ProximityInfoParams::MAX_SKIP_PROBABILITY];
696         ASSERT(skipProbability >= 0.0f);
697         ASSERT(skipProbability <= ProximityInfoParams::MAX_SKIP_PROBABILITY);
698         (*charProbabilities)[i][NOT_AN_INDEX] = skipProbability;
699 
700         // Second, calculates key probabilities by dividing the rest probability
701         // (1.0f - skipProbability).
702         const float inputCharProbability = 1.0f - skipProbability;
703 
704         const float speedMultipliedByAngleRate = std::min(speedRate * currentAngle / M_PI_F
705                 * ProximityInfoParams::SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION,
706                         ProximityInfoParams::MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION);
707         const float speedMultipliedByNearestKeyDistanceRate = std::min(
708                 speedRate * nearestKeyDistance
709                         * ProximityInfoParams::SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION,
710                                 ProximityInfoParams::MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION);
711         const float sigma = (speedMultipliedByAngleRate + speedMultipliedByNearestKeyDistanceRate
712                 + ProximityInfoParams::MIN_STANDARD_DEVIATION) * mostCommonKeyWidth;
713         float theta = 0.0f;
714         // TODO: Use different metrics to compute sigmas.
715         float sigmaX = sigma;
716         float sigmaY = sigma;
717         if (i == 0 && i != sampledInputSize - 1) {
718             // First point
719             theta = getDirection(sampledInputXs, sampledInputYs, i + 1, i);
720             sigmaX *= ProximityInfoParams::STANDARD_DEVIATION_X_WEIGHT_FOR_FIRST;
721             sigmaY *= ProximityInfoParams::STANDARD_DEVIATION_Y_WEIGHT_FOR_FIRST;
722         } else {
723             if (i == sampledInputSize - 1) {
724                 // Last point
725                 sigmaX *= ProximityInfoParams::STANDARD_DEVIATION_X_WEIGHT_FOR_LAST;
726                 sigmaY *= ProximityInfoParams::STANDARD_DEVIATION_Y_WEIGHT_FOR_LAST;
727             } else {
728                 sigmaX *= ProximityInfoParams::STANDARD_DEVIATION_X_WEIGHT;
729                 sigmaY *= ProximityInfoParams::STANDARD_DEVIATION_Y_WEIGHT;
730             }
731             theta = getDirection(sampledInputXs, sampledInputYs, i, i - 1);
732         }
733         NormalDistribution2D distribution((*sampledInputXs)[i], sigmaX, (*sampledInputYs)[i],
734                 sigmaY, theta);
735         // Summing up probability densities of all near keys.
736         float sumOfProbabilityDensities = 0.0f;
737         for (int j = 0; j < keyCount; ++j) {
738             sumOfProbabilityDensities += distribution.getProbabilityDensity(
739                     proximityInfo->getKeyCenterXOfKeyIdG(j,
740                             NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
741                     proximityInfo->getKeyCenterYOfKeyIdG(j,
742                             NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
743         }
744 
745         // Split the probability of an input point to keys that are close to the input point.
746         for (int j = 0; j < keyCount; ++j) {
747             const float probabilityDensity = distribution.getProbabilityDensity(
748                     proximityInfo->getKeyCenterXOfKeyIdG(j,
749                             NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
750                     proximityInfo->getKeyCenterYOfKeyIdG(j,
751                             NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
752             const float probability = inputCharProbability * probabilityDensity
753                     / sumOfProbabilityDensities;
754             (*charProbabilities)[i][j] = probability;
755         }
756     }
757 
758     if (DEBUG_POINTS_PROBABILITY) {
759         for (int i = 0; i < sampledInputSize; ++i) {
760             std::stringstream sstream;
761             sstream << i << ", ";
762             sstream << "(" << (*sampledInputXs)[i] << ", " << (*sampledInputYs)[i] << "), ";
763             sstream << "Speed: "<< (*sampledSpeedRates)[i] << ", ";
764             sstream << "Angle: "<< getPointAngle(sampledInputXs, sampledInputYs, i) << ", \n";
765 
766             for (std::unordered_map<int, float>::iterator it = (*charProbabilities)[i].begin();
767                     it != (*charProbabilities)[i].end(); ++it) {
768                 if (it->first == NOT_AN_INDEX) {
769                     sstream << it->first
770                             << "(skip):"
771                             << it->second
772                             << "\n";
773                 } else {
774                     sstream << it->first
775                             << "("
776                             //<< static_cast<char>(mProximityInfo->getCodePointOf(it->first))
777                             << "):"
778                             << it->second
779                             << "\n";
780                 }
781             }
782             AKLOGI("%s", sstream.str().c_str());
783         }
784     }
785 
786     // Decrease key probabilities of points which don't have the highest probability of that key
787     // among nearby points. Probabilities of the first point and the last point are not suppressed.
788     for (int i = std::max(start, 1); i < sampledInputSize; ++i) {
789         for (int j = i + 1; j < sampledInputSize; ++j) {
790             if (!suppressCharProbabilities(
791                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
792                     charProbabilities)) {
793                 break;
794             }
795         }
796         for (int j = i - 1; j >= std::max(start, 0); --j) {
797             if (!suppressCharProbabilities(
798                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
799                     charProbabilities)) {
800                 break;
801             }
802         }
803     }
804 
805     // Converting from raw probabilities to log probabilities to calculate spatial distance.
806     for (int i = start; i < sampledInputSize; ++i) {
807         for (int j = 0; j < keyCount; ++j) {
808             std::unordered_map<int, float>::iterator it = (*charProbabilities)[i].find(j);
809             if (it == (*charProbabilities)[i].end()){
810                 continue;
811             } else if(it->second < ProximityInfoParams::MIN_PROBABILITY) {
812                 // Erases from near keys vector because it has very low probability.
813                 (*charProbabilities)[i].erase(j);
814             } else {
815                 it->second = -logf(it->second);
816             }
817         }
818         (*charProbabilities)[i][NOT_AN_INDEX] = -logf((*charProbabilities)[i][NOT_AN_INDEX]);
819     }
820 }
821 
updateSampledSearchKeySets(const ProximityInfo * const proximityInfo,const int sampledInputSize,const int lastSavedInputSize,const std::vector<int> * const sampledLengthCache,const std::vector<std::unordered_map<int,float>> * const charProbabilities,std::vector<NearKeycodesSet> * sampledSearchKeySets,std::vector<std::vector<int>> * sampledSearchKeyVectors)822 /* static */ void ProximityInfoStateUtils::updateSampledSearchKeySets(
823         const ProximityInfo *const proximityInfo, const int sampledInputSize,
824         const int lastSavedInputSize, const std::vector<int> *const sampledLengthCache,
825         const std::vector<std::unordered_map<int, float>> *const charProbabilities,
826         std::vector<NearKeycodesSet> *sampledSearchKeySets,
827         std::vector<std::vector<int>> *sampledSearchKeyVectors) {
828     sampledSearchKeySets->resize(sampledInputSize);
829     sampledSearchKeyVectors->resize(sampledInputSize);
830     const int readForwordLength = static_cast<int>(
831             hypotf(proximityInfo->getKeyboardWidth(), proximityInfo->getKeyboardHeight())
832                     * ProximityInfoParams::SEARCH_KEY_RADIUS_RATIO);
833     for (int i = 0; i < sampledInputSize; ++i) {
834         if (i >= lastSavedInputSize) {
835             (*sampledSearchKeySets)[i].reset();
836         }
837         for (int j = std::max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
838             // TODO: Investigate if this is required. This may not fail.
839             if ((*sampledLengthCache)[j] - (*sampledLengthCache)[i] >= readForwordLength) {
840                 break;
841             }
842             for(const auto& charProbability : charProbabilities->at(j)) {
843                 if (charProbability.first == NOT_AN_INDEX) {
844                     continue;
845                 }
846                 (*sampledSearchKeySets)[i].set(charProbability.first);
847             }
848         }
849     }
850     const int keyCount = proximityInfo->getKeyCount();
851     for (int i = 0; i < sampledInputSize; ++i) {
852         std::vector<int> *searchKeyVector = &(*sampledSearchKeyVectors)[i];
853         searchKeyVector->clear();
854         for (int j = 0; j < keyCount; ++j) {
855             if ((*sampledSearchKeySets)[i].test(j)) {
856                 const int keyCodePoint = proximityInfo->getCodePointOf(j);
857                 if (std::find(searchKeyVector->begin(), searchKeyVector->end(), keyCodePoint)
858                         == searchKeyVector->end()) {
859                     searchKeyVector->push_back(keyCodePoint);
860                 }
861             }
862         }
863     }
864 }
865 
866 // Decreases char probabilities of index0 by checking probabilities of a near point (index1) and
867 // increases char probabilities of index1 by checking probabilities of index0.
suppressCharProbabilities(const int mostCommonKeyWidth,const int sampledInputSize,const std::vector<int> * const lengthCache,const int index0,const int index1,std::vector<std::unordered_map<int,float>> * charProbabilities)868 /* static */ bool ProximityInfoStateUtils::suppressCharProbabilities(const int mostCommonKeyWidth,
869         const int sampledInputSize, const std::vector<int> *const lengthCache,
870         const int index0, const int index1,
871         std::vector<std::unordered_map<int, float>> *charProbabilities) {
872     ASSERT(0 <= index0 && index0 < sampledInputSize);
873     ASSERT(0 <= index1 && index1 < sampledInputSize);
874     const float keyWidthFloat = static_cast<float>(mostCommonKeyWidth);
875     const float diff = fabsf(static_cast<float>((*lengthCache)[index0] - (*lengthCache)[index1]));
876     if (diff > keyWidthFloat * ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT) {
877         return false;
878     }
879     const float suppressionRate = ProximityInfoParams::MIN_SUPPRESSION_RATE
880             + diff / keyWidthFloat / ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT
881                     * ProximityInfoParams::SUPPRESSION_WEIGHT;
882     for (std::unordered_map<int, float>::iterator it = (*charProbabilities)[index0].begin();
883             it != (*charProbabilities)[index0].end(); ++it) {
884         std::unordered_map<int, float>::iterator it2 = (*charProbabilities)[index1].find(it->first);
885         if (it2 != (*charProbabilities)[index1].end() && it->second < it2->second) {
886             const float newProbability = it->second * suppressionRate;
887             const float suppression = it->second - newProbability;
888             it->second = newProbability;
889             // mCharProbabilities[index0][NOT_AN_INDEX] is the probability of skipping this point.
890             (*charProbabilities)[index0][NOT_AN_INDEX] += suppression;
891 
892             // Add the probability of the same key nearby index1
893             const float probabilityGain = std::min(suppression
894                     * ProximityInfoParams::SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN,
895                     (*charProbabilities)[index1][NOT_AN_INDEX]
896                             * ProximityInfoParams::SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN);
897             it2->second += probabilityGain;
898             (*charProbabilities)[index1][NOT_AN_INDEX] -= probabilityGain;
899         }
900     }
901     return true;
902 }
903 
checkAndReturnIsContinuousSuggestionPossible(const int inputSize,const int * const xCoordinates,const int * const yCoordinates,const int * const times,const int sampledInputSize,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const std::vector<int> * const sampledTimes,const std::vector<int> * const sampledInputIndices)904 /* static */ bool ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
905         const int inputSize, const int *const xCoordinates, const int *const yCoordinates,
906         const int *const times, const int sampledInputSize,
907         const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs,
908         const std::vector<int> *const sampledTimes,
909         const std::vector<int> *const sampledInputIndices) {
910     if (inputSize < sampledInputSize) {
911         return false;
912     }
913     for (int i = 0; i < sampledInputSize; ++i) {
914         const int index = (*sampledInputIndices)[i];
915         if (index >= inputSize) {
916             return false;
917         }
918         if (xCoordinates[index] != (*sampledInputXs)[i]
919                 || yCoordinates[index] != (*sampledInputYs)[i]) {
920             return false;
921         }
922         if (!times) {
923             continue;
924         }
925         if (times[index] != (*sampledTimes)[i]) {
926             return false;
927         }
928     }
929     return true;
930 }
931 
932 // Get a word that is detected by tracing the most probable string into codePointBuf and
933 // returns probability of generating the word.
getMostProbableString(const ProximityInfo * const proximityInfo,const int sampledInputSize,const std::vector<std::unordered_map<int,float>> * const charProbabilities,int * const codePointBuf)934 /* static */ float ProximityInfoStateUtils::getMostProbableString(
935         const ProximityInfo *const proximityInfo, const int sampledInputSize,
936         const std::vector<std::unordered_map<int, float>> *const charProbabilities,
937         int *const codePointBuf) {
938     ASSERT(sampledInputSize >= 0);
939     memset(codePointBuf, 0, sizeof(codePointBuf[0]) * MAX_WORD_LENGTH);
940     int index = 0;
941     float sumLogProbability = 0.0f;
942     // TODO: Current implementation is greedy algorithm. DP would be efficient for many cases.
943     for (int i = 0; i < sampledInputSize && index < MAX_WORD_LENGTH - 1; ++i) {
944         float minLogProbability = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
945         int character = NOT_AN_INDEX;
946         for (std::unordered_map<int, float>::const_iterator it = (*charProbabilities)[i].begin();
947                 it != (*charProbabilities)[i].end(); ++it) {
948             const float logProbability = (it->first != NOT_AN_INDEX)
949                     ? it->second + ProximityInfoParams::DEMOTION_LOG_PROBABILITY : it->second;
950             if (logProbability < minLogProbability) {
951                 minLogProbability = logProbability;
952                 character = it->first;
953             }
954         }
955         if (character != NOT_AN_INDEX) {
956             const int codePoint = proximityInfo->getCodePointOf(character);
957             if (codePoint == NOT_A_CODE_POINT) {
958                 AKLOGE("Key index(%d) is not found. Cannot construct most probable string",
959                         character);
960                 ASSERT(false);
961                 // Make the length zero, which means most probable string won't be used.
962                 index = 0;
963                 break;
964             }
965             codePointBuf[index] = codePoint;
966             index++;
967         }
968         sumLogProbability += minLogProbability;
969     }
970     codePointBuf[index] = '\0';
971     return sumLogProbability;
972 }
973 
dump(const bool isGeometric,const int inputSize,const int * const inputXCoordinates,const int * const inputYCoordinates,const int sampledInputSize,const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const std::vector<int> * const sampledTimes,const std::vector<float> * const sampledSpeedRates,const std::vector<int> * const sampledBeelineSpeedPercentiles)974 /* static */ void ProximityInfoStateUtils::dump(const bool isGeometric, const int inputSize,
975         const int *const inputXCoordinates, const int *const inputYCoordinates,
976         const int sampledInputSize, const std::vector<int> *const sampledInputXs,
977         const std::vector<int> *const sampledInputYs,
978         const std::vector<int> *const sampledTimes,
979         const std::vector<float> *const sampledSpeedRates,
980         const std::vector<int> *const sampledBeelineSpeedPercentiles) {
981     if (DEBUG_GEO_FULL) {
982         for (int i = 0; i < sampledInputSize; ++i) {
983             AKLOGI("Sampled(%d): x = %d, y = %d, time = %d", i, (*sampledInputXs)[i],
984                     (*sampledInputYs)[i], sampledTimes ? (*sampledTimes)[i] : -1);
985         }
986     }
987 
988     std::stringstream originalX, originalY, sampledX, sampledY;
989     for (int i = 0; i < inputSize; ++i) {
990         originalX << inputXCoordinates[i];
991         originalY << inputYCoordinates[i];
992         if (i != inputSize - 1) {
993             originalX << ";";
994             originalY << ";";
995         }
996     }
997     AKLOGI("===== sampled points =====");
998     for (int i = 0; i < sampledInputSize; ++i) {
999         if (isGeometric) {
1000             AKLOGI("%d: x = %d, y = %d, time = %d, relative speed = %.4f, beeline speed = %d",
1001                     i, (*sampledInputXs)[i], (*sampledInputYs)[i], (*sampledTimes)[i],
1002                     (*sampledSpeedRates)[i], (*sampledBeelineSpeedPercentiles)[i]);
1003         }
1004         sampledX << (*sampledInputXs)[i];
1005         sampledY << (*sampledInputYs)[i];
1006         if (i != sampledInputSize - 1) {
1007             sampledX << ";";
1008             sampledY << ";";
1009         }
1010     }
1011     AKLOGI("original points:\n%s, %s,\nsampled points:\n%s, %s,\n",
1012             originalX.str().c_str(), originalY.str().c_str(), sampledX.str().c_str(),
1013             sampledY.str().c_str());
1014 }
1015 } // namespace latinime
1016