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 tempBeelineDistance = 0;
494     int start = actualInputIndex;
495     // lookup forward
496     while (start > 0 && tempBeelineDistance < lookupRadius) {
497         --start;
498         tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[start],
499                 yCoordinates[start]);
500     }
501     // Exclusive unless this is an edge point
502     if (start > 0 && start < actualInputIndex) {
503         ++start;
504     }
505     tempBeelineDistance = 0;
506     int end = actualInputIndex;
507     // lookup backward
508     while (end < (inputSize - 1) && tempBeelineDistance < lookupRadius) {
509         ++end;
510         tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[end],
511                 yCoordinates[end]);
512     }
513     // Exclusive unless this is an edge point
514     if (end > actualInputIndex && end < (inputSize - 1)) {
515         --end;
516     }
517 
518     if (start >= end) {
519         if (DEBUG_DOUBLE_LETTER) {
520             AKLOGI("--- double letter: start == end %d", start);
521         }
522         return 1.0f;
523     }
524 
525     const int x2 = xCoordinates[start];
526     const int y2 = yCoordinates[start];
527     const int x3 = xCoordinates[end];
528     const int y3 = yCoordinates[end];
529     const int beelineDistance = GeometryUtils::getDistanceInt(x2, y2, x3, y3);
530     int adjustedStartTime = times[start];
531     if (start == 0 && actualInputIndex == 0 && inputSize > 1) {
532         adjustedStartTime += ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
533     }
534     int adjustedEndTime = times[end];
535     if (end == (inputSize - 1) && inputSize > 1) {
536         adjustedEndTime -= ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
537     }
538     const int time = adjustedEndTime - adjustedStartTime;
539     if (time <= 0) {
540         return 1.0f;
541     }
542 
543     if (time >= ProximityInfoParams::STRONG_DOUBLE_LETTER_TIME_MILLIS){
544         return 0.0f;
545     }
546     if (DEBUG_DOUBLE_LETTER) {
547         AKLOGI("--- (%d, %d) double letter: start = %d, end = %d, dist = %d, time = %d,"
548                 " speed = %f, ave = %f, val = %f, start time = %d, end time = %d",
549                 id, (*sampledInputIndices)[id], start, end, beelineDistance, time,
550                 (static_cast<float>(beelineDistance) / static_cast<float>(time)), averageSpeed,
551                 ((static_cast<float>(beelineDistance) / static_cast<float>(time))
552                         / averageSpeed), adjustedStartTime, adjustedEndTime);
553     }
554     // Offset 1%
555     // TODO: Detect double letter more smartly
556     return 0.01f + static_cast<float>(beelineDistance) / static_cast<float>(time) / averageSpeed;
557 }
558 
getPointAngle(const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int index)559 /* static */ float ProximityInfoStateUtils::getPointAngle(
560         const std::vector<int> *const sampledInputXs,
561         const std::vector<int> *const sampledInputYs, const int index) {
562     if (!sampledInputXs || !sampledInputYs) {
563         return 0.0f;
564     }
565     const int sampledInputSize = sampledInputXs->size();
566     if (index <= 0 || index >= sampledInputSize - 1) {
567         return 0.0f;
568     }
569     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index - 1, index);
570     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index, index + 1);
571     const float directionDiff = GeometryUtils::getAngleDiff(previousDirection, nextDirection);
572     return directionDiff;
573 }
574 
getPointsAngle(const std::vector<int> * const sampledInputXs,const std::vector<int> * const sampledInputYs,const int index0,const int index1,const int index2)575 /* static */ float ProximityInfoStateUtils::getPointsAngle(
576         const std::vector<int> *const sampledInputXs,
577         const std::vector<int> *const sampledInputYs,
578         const int index0, const int index1, const int index2) {
579     if (!sampledInputXs || !sampledInputYs) {
580         return 0.0f;
581     }
582     const int sampledInputSize = sampledInputXs->size();
583     if (index0 < 0 || index0 > sampledInputSize - 1) {
584         return 0.0f;
585     }
586     if (index1 < 0 || index1 > sampledInputSize - 1) {
587         return 0.0f;
588     }
589     if (index2 < 0 || index2 > sampledInputSize - 1) {
590         return 0.0f;
591     }
592     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index0, index1);
593     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index1, index2);
594     return GeometryUtils::getAngleDiff(previousDirection, nextDirection);
595 }
596 
597 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
598 // wrong to compare with mMaxPointToKeyLength.
getPointToKeyByIdLength(const float maxPointToKeyLength,const std::vector<float> * const sampledNormalizedSquaredLengthCache,const int keyCount,const int inputIndex,const int keyId)599 /* static */ float ProximityInfoStateUtils::getPointToKeyByIdLength(const float maxPointToKeyLength,
600         const std::vector<float> *const sampledNormalizedSquaredLengthCache, const int keyCount,
601         const int inputIndex, const int keyId) {
602     if (keyId != NOT_AN_INDEX) {
603         const int index = inputIndex * keyCount + keyId;
604         return std::min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
605     }
606     // If the char is not a key on the keyboard then return the max length.
607     return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
608 }
609 
610 // Updates probabilities of aligning to some keys and skipping.
611 // 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)612 /* static */ void ProximityInfoStateUtils::updateAlignPointProbabilities(
613         const float maxPointToKeyLength, const int mostCommonKeyWidth, const int keyCount,
614         const int start, const int sampledInputSize, const std::vector<int> *const sampledInputXs,
615         const std::vector<int> *const sampledInputYs,
616         const std::vector<float> *const sampledSpeedRates,
617         const std::vector<int> *const sampledLengthCache,
618         const std::vector<float> *const sampledNormalizedSquaredLengthCache,
619         const ProximityInfo *const proximityInfo,
620         std::vector<std::unordered_map<int, float>> *charProbabilities) {
621     charProbabilities->resize(sampledInputSize);
622     // Calculates probabilities of using a point as a correlated point with the character
623     // for each point.
624     for (int i = start; i < sampledInputSize; ++i) {
625         (*charProbabilities)[i].clear();
626         // First, calculates skip probability. Starts from MAX_SKIP_PROBABILITY.
627         // Note that all values that are multiplied to this probability should be in [0.0, 1.0];
628         float skipProbability = ProximityInfoParams::MAX_SKIP_PROBABILITY;
629 
630         const float currentAngle = getPointAngle(sampledInputXs, sampledInputYs, i);
631         const float speedRate = (*sampledSpeedRates)[i];
632 
633         float nearestKeyDistance = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
634         for (int j = 0; j < keyCount; ++j) {
635             const float distance = getPointToKeyByIdLength(
636                     maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j);
637             if (distance < nearestKeyDistance) {
638                 nearestKeyDistance = distance;
639             }
640         }
641 
642         if (i == 0) {
643             skipProbability *= std::min(1.0f,
644                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
645                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
646             // Promote the first point
647             skipProbability *= ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY;
648         } else if (i == sampledInputSize - 1) {
649             skipProbability *= std::min(1.0f,
650                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT_FOR_LAST
651                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS_FOR_LAST);
652             // Promote the last point
653             skipProbability *= ProximityInfoParams::SKIP_LAST_POINT_PROBABILITY;
654         } else {
655             // If the current speed is relatively slower than adjacent keys, we promote this point.
656             if ((*sampledSpeedRates)[i - 1] - ProximityInfoParams::SPEED_MARGIN > speedRate
657                     && speedRate
658                             < (*sampledSpeedRates)[i + 1] - ProximityInfoParams::SPEED_MARGIN) {
659                 if (currentAngle < ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
660                     skipProbability *= std::min(1.0f, speedRate
661                             * ProximityInfoParams::SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY);
662                 } else {
663                     // If the angle is small enough, we promote this point more. (e.g. pit vs put)
664                     skipProbability *= std::min(1.0f,
665                             speedRate * ProximityInfoParams::SPEED_WEIGHT_FOR_SKIP_PROBABILITY
666                                     + ProximityInfoParams::MIN_SPEED_RATE_FOR_SKIP_PROBABILITY);
667                 }
668             }
669 
670             skipProbability *= std::min(1.0f,
671                     speedRate * nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
672                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
673 
674             // Adjusts skip probability by a rate depending on angle.
675             // ANGLE_RATE of skipProbability is adjusted by current angle.
676             skipProbability *= (M_PI_F - currentAngle) / M_PI_F * ProximityInfoParams::ANGLE_WEIGHT
677                     + (1.0f - ProximityInfoParams::ANGLE_WEIGHT);
678             if (currentAngle > ProximityInfoParams::DEEP_CORNER_ANGLE_THRESHOLD) {
679                 skipProbability *= ProximityInfoParams::SKIP_DEEP_CORNER_PROBABILITY;
680             }
681             // We assume the angle of this point is the angle for point[i], point[i - 2]
682             // and point[i - 3]. The reason why we don't use the angle for point[i], point[i - 1]
683             // and point[i - 2] is this angle can be more affected by the noise.
684             const float prevAngle = getPointsAngle(sampledInputXs, sampledInputYs, i, i - 2, i - 3);
685             if (i >= 3 && prevAngle < ProximityInfoParams::STRAIGHT_ANGLE_THRESHOLD
686                     && currentAngle > ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
687                 skipProbability *= ProximityInfoParams::SKIP_CORNER_PROBABILITY;
688             }
689         }
690 
691         // probabilities must be in [0.0, ProximityInfoParams::MAX_SKIP_PROBABILITY];
692         ASSERT(skipProbability >= 0.0f);
693         ASSERT(skipProbability <= ProximityInfoParams::MAX_SKIP_PROBABILITY);
694         (*charProbabilities)[i][NOT_AN_INDEX] = skipProbability;
695 
696         // Second, calculates key probabilities by dividing the rest probability
697         // (1.0f - skipProbability).
698         const float inputCharProbability = 1.0f - skipProbability;
699 
700         const float speedMultipliedByAngleRate = std::min(speedRate * currentAngle / M_PI_F
701                 * ProximityInfoParams::SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION,
702                         ProximityInfoParams::MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION);
703         const float speedMultipliedByNearestKeyDistanceRate = std::min(
704                 speedRate * nearestKeyDistance
705                         * ProximityInfoParams::SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION,
706                                 ProximityInfoParams::MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION);
707         const float sigma = (speedMultipliedByAngleRate + speedMultipliedByNearestKeyDistanceRate
708                 + ProximityInfoParams::MIN_STANDARD_DEVIATION) * mostCommonKeyWidth;
709         float theta = 0.0f;
710         // TODO: Use different metrics to compute sigmas.
711         float sigmaX = sigma;
712         float sigmaY = sigma;
713         if (i == 0 && i != sampledInputSize - 1) {
714             // First point
715             theta = getDirection(sampledInputXs, sampledInputYs, i + 1, i);
716             sigmaX *= ProximityInfoParams::STANDARD_DEVIATION_X_WEIGHT_FOR_FIRST;
717             sigmaY *= ProximityInfoParams::STANDARD_DEVIATION_Y_WEIGHT_FOR_FIRST;
718         } else {
719             if (i == sampledInputSize - 1) {
720                 // Last point
721                 sigmaX *= ProximityInfoParams::STANDARD_DEVIATION_X_WEIGHT_FOR_LAST;
722                 sigmaY *= ProximityInfoParams::STANDARD_DEVIATION_Y_WEIGHT_FOR_LAST;
723             } else {
724                 sigmaX *= ProximityInfoParams::STANDARD_DEVIATION_X_WEIGHT;
725                 sigmaY *= ProximityInfoParams::STANDARD_DEVIATION_Y_WEIGHT;
726             }
727             theta = getDirection(sampledInputXs, sampledInputYs, i, i - 1);
728         }
729         NormalDistribution2D distribution((*sampledInputXs)[i], sigmaX, (*sampledInputYs)[i],
730                 sigmaY, theta);
731         // Summing up probability densities of all near keys.
732         float sumOfProbabilityDensities = 0.0f;
733         for (int j = 0; j < keyCount; ++j) {
734             sumOfProbabilityDensities += distribution.getProbabilityDensity(
735                     proximityInfo->getKeyCenterXOfKeyIdG(j,
736                             NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
737                     proximityInfo->getKeyCenterYOfKeyIdG(j,
738                             NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
739         }
740 
741         // Split the probability of an input point to keys that are close to the input point.
742         for (int j = 0; j < keyCount; ++j) {
743             const float probabilityDensity = distribution.getProbabilityDensity(
744                     proximityInfo->getKeyCenterXOfKeyIdG(j,
745                             NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
746                     proximityInfo->getKeyCenterYOfKeyIdG(j,
747                             NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
748             const float probability = inputCharProbability * probabilityDensity
749                     / sumOfProbabilityDensities;
750             (*charProbabilities)[i][j] = probability;
751         }
752     }
753 
754     if (DEBUG_POINTS_PROBABILITY) {
755         for (int i = 0; i < sampledInputSize; ++i) {
756             std::stringstream sstream;
757             sstream << i << ", ";
758             sstream << "(" << (*sampledInputXs)[i] << ", " << (*sampledInputYs)[i] << "), ";
759             sstream << "Speed: "<< (*sampledSpeedRates)[i] << ", ";
760             sstream << "Angle: "<< getPointAngle(sampledInputXs, sampledInputYs, i) << ", \n";
761 
762             for (std::unordered_map<int, float>::iterator it = (*charProbabilities)[i].begin();
763                     it != (*charProbabilities)[i].end(); ++it) {
764                 if (it->first == NOT_AN_INDEX) {
765                     sstream << it->first
766                             << "(skip):"
767                             << it->second
768                             << "\n";
769                 } else {
770                     sstream << it->first
771                             << "("
772                             //<< static_cast<char>(mProximityInfo->getCodePointOf(it->first))
773                             << "):"
774                             << it->second
775                             << "\n";
776                 }
777             }
778             AKLOGI("%s", sstream.str().c_str());
779         }
780     }
781 
782     // Decrease key probabilities of points which don't have the highest probability of that key
783     // among nearby points. Probabilities of the first point and the last point are not suppressed.
784     for (int i = std::max(start, 1); i < sampledInputSize; ++i) {
785         for (int j = i + 1; j < sampledInputSize; ++j) {
786             if (!suppressCharProbabilities(
787                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
788                     charProbabilities)) {
789                 break;
790             }
791         }
792         for (int j = i - 1; j >= std::max(start, 0); --j) {
793             if (!suppressCharProbabilities(
794                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
795                     charProbabilities)) {
796                 break;
797             }
798         }
799     }
800 
801     // Converting from raw probabilities to log probabilities to calculate spatial distance.
802     for (int i = start; i < sampledInputSize; ++i) {
803         for (int j = 0; j < keyCount; ++j) {
804             std::unordered_map<int, float>::iterator it = (*charProbabilities)[i].find(j);
805             if (it == (*charProbabilities)[i].end()){
806                 continue;
807             } else if(it->second < ProximityInfoParams::MIN_PROBABILITY) {
808                 // Erases from near keys vector because it has very low probability.
809                 (*charProbabilities)[i].erase(j);
810             } else {
811                 it->second = -logf(it->second);
812             }
813         }
814         (*charProbabilities)[i][NOT_AN_INDEX] = -logf((*charProbabilities)[i][NOT_AN_INDEX]);
815     }
816 }
817 
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)818 /* static */ void ProximityInfoStateUtils::updateSampledSearchKeySets(
819         const ProximityInfo *const proximityInfo, const int sampledInputSize,
820         const int lastSavedInputSize, const std::vector<int> *const sampledLengthCache,
821         const std::vector<std::unordered_map<int, float>> *const charProbabilities,
822         std::vector<NearKeycodesSet> *sampledSearchKeySets,
823         std::vector<std::vector<int>> *sampledSearchKeyVectors) {
824     sampledSearchKeySets->resize(sampledInputSize);
825     sampledSearchKeyVectors->resize(sampledInputSize);
826     const int readForwordLength = static_cast<int>(
827             hypotf(proximityInfo->getKeyboardWidth(), proximityInfo->getKeyboardHeight())
828                     * ProximityInfoParams::SEARCH_KEY_RADIUS_RATIO);
829     for (int i = 0; i < sampledInputSize; ++i) {
830         if (i >= lastSavedInputSize) {
831             (*sampledSearchKeySets)[i].reset();
832         }
833         for (int j = std::max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
834             // TODO: Investigate if this is required. This may not fail.
835             if ((*sampledLengthCache)[j] - (*sampledLengthCache)[i] >= readForwordLength) {
836                 break;
837             }
838             for(const auto& charProbability : charProbabilities->at(j)) {
839                 if (charProbability.first == NOT_AN_INDEX) {
840                     continue;
841                 }
842                 (*sampledSearchKeySets)[i].set(charProbability.first);
843             }
844         }
845     }
846     const int keyCount = proximityInfo->getKeyCount();
847     for (int i = 0; i < sampledInputSize; ++i) {
848         std::vector<int> *searchKeyVector = &(*sampledSearchKeyVectors)[i];
849         searchKeyVector->clear();
850         for (int j = 0; j < keyCount; ++j) {
851             if ((*sampledSearchKeySets)[i].test(j)) {
852                 const int keyCodePoint = proximityInfo->getCodePointOf(j);
853                 if (std::find(searchKeyVector->begin(), searchKeyVector->end(), keyCodePoint)
854                         == searchKeyVector->end()) {
855                     searchKeyVector->push_back(keyCodePoint);
856                 }
857             }
858         }
859     }
860 }
861 
862 // Decreases char probabilities of index0 by checking probabilities of a near point (index1) and
863 // 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)864 /* static */ bool ProximityInfoStateUtils::suppressCharProbabilities(const int mostCommonKeyWidth,
865         const int sampledInputSize, const std::vector<int> *const lengthCache,
866         const int index0, const int index1,
867         std::vector<std::unordered_map<int, float>> *charProbabilities) {
868     ASSERT(0 <= index0 && index0 < sampledInputSize);
869     ASSERT(0 <= index1 && index1 < sampledInputSize);
870     const float keyWidthFloat = static_cast<float>(mostCommonKeyWidth);
871     const float diff = fabsf(static_cast<float>((*lengthCache)[index0] - (*lengthCache)[index1]));
872     if (diff > keyWidthFloat * ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT) {
873         return false;
874     }
875     const float suppressionRate = ProximityInfoParams::MIN_SUPPRESSION_RATE
876             + diff / keyWidthFloat / ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT
877                     * ProximityInfoParams::SUPPRESSION_WEIGHT;
878     for (std::unordered_map<int, float>::iterator it = (*charProbabilities)[index0].begin();
879             it != (*charProbabilities)[index0].end(); ++it) {
880         std::unordered_map<int, float>::iterator it2 = (*charProbabilities)[index1].find(it->first);
881         if (it2 != (*charProbabilities)[index1].end() && it->second < it2->second) {
882             const float newProbability = it->second * suppressionRate;
883             const float suppression = it->second - newProbability;
884             it->second = newProbability;
885             // mCharProbabilities[index0][NOT_AN_INDEX] is the probability of skipping this point.
886             (*charProbabilities)[index0][NOT_AN_INDEX] += suppression;
887 
888             // Add the probability of the same key nearby index1
889             const float probabilityGain = std::min(suppression
890                     * ProximityInfoParams::SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN,
891                     (*charProbabilities)[index1][NOT_AN_INDEX]
892                             * ProximityInfoParams::SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN);
893             it2->second += probabilityGain;
894             (*charProbabilities)[index1][NOT_AN_INDEX] -= probabilityGain;
895         }
896     }
897     return true;
898 }
899 
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)900 /* static */ bool ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
901         const int inputSize, const int *const xCoordinates, const int *const yCoordinates,
902         const int *const times, const int sampledInputSize,
903         const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs,
904         const std::vector<int> *const sampledTimes,
905         const std::vector<int> *const sampledInputIndices) {
906     if (inputSize < sampledInputSize) {
907         return false;
908     }
909     for (int i = 0; i < sampledInputSize; ++i) {
910         const int index = (*sampledInputIndices)[i];
911         if (index >= inputSize) {
912             return false;
913         }
914         if (xCoordinates[index] != (*sampledInputXs)[i]
915                 || yCoordinates[index] != (*sampledInputYs)[i]) {
916             return false;
917         }
918         if (!times) {
919             continue;
920         }
921         if (times[index] != (*sampledTimes)[i]) {
922             return false;
923         }
924     }
925     return true;
926 }
927 
928 // Get a word that is detected by tracing the most probable string into codePointBuf and
929 // 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)930 /* static */ float ProximityInfoStateUtils::getMostProbableString(
931         const ProximityInfo *const proximityInfo, const int sampledInputSize,
932         const std::vector<std::unordered_map<int, float>> *const charProbabilities,
933         int *const codePointBuf) {
934     ASSERT(sampledInputSize >= 0);
935     memset(codePointBuf, 0, sizeof(codePointBuf[0]) * MAX_WORD_LENGTH);
936     int index = 0;
937     float sumLogProbability = 0.0f;
938     // TODO: Current implementation is greedy algorithm. DP would be efficient for many cases.
939     for (int i = 0; i < sampledInputSize && index < MAX_WORD_LENGTH - 1; ++i) {
940         float minLogProbability = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
941         int character = NOT_AN_INDEX;
942         for (std::unordered_map<int, float>::const_iterator it = (*charProbabilities)[i].begin();
943                 it != (*charProbabilities)[i].end(); ++it) {
944             const float logProbability = (it->first != NOT_AN_INDEX)
945                     ? it->second + ProximityInfoParams::DEMOTION_LOG_PROBABILITY : it->second;
946             if (logProbability < minLogProbability) {
947                 minLogProbability = logProbability;
948                 character = it->first;
949             }
950         }
951         if (character != NOT_AN_INDEX) {
952             const int codePoint = proximityInfo->getCodePointOf(character);
953             if (codePoint == NOT_A_CODE_POINT) {
954                 AKLOGE("Key index(%d) is not found. Cannot construct most probable string",
955                         character);
956                 ASSERT(false);
957                 // Make the length zero, which means most probable string won't be used.
958                 index = 0;
959                 break;
960             }
961             codePointBuf[index] = codePoint;
962             index++;
963         }
964         sumLogProbability += minLogProbability;
965     }
966     codePointBuf[index] = '\0';
967     return sumLogProbability;
968 }
969 
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)970 /* static */ void ProximityInfoStateUtils::dump(const bool isGeometric, const int inputSize,
971         const int *const inputXCoordinates, const int *const inputYCoordinates,
972         const int sampledInputSize, const std::vector<int> *const sampledInputXs,
973         const std::vector<int> *const sampledInputYs,
974         const std::vector<int> *const sampledTimes,
975         const std::vector<float> *const sampledSpeedRates,
976         const std::vector<int> *const sampledBeelineSpeedPercentiles) {
977     if (DEBUG_GEO_FULL) {
978         for (int i = 0; i < sampledInputSize; ++i) {
979             AKLOGI("Sampled(%d): x = %d, y = %d, time = %d", i, (*sampledInputXs)[i],
980                     (*sampledInputYs)[i], sampledTimes ? (*sampledTimes)[i] : -1);
981         }
982     }
983 
984     std::stringstream originalX, originalY, sampledX, sampledY;
985     for (int i = 0; i < inputSize; ++i) {
986         originalX << inputXCoordinates[i];
987         originalY << inputYCoordinates[i];
988         if (i != inputSize - 1) {
989             originalX << ";";
990             originalY << ";";
991         }
992     }
993     AKLOGI("===== sampled points =====");
994     for (int i = 0; i < sampledInputSize; ++i) {
995         if (isGeometric) {
996             AKLOGI("%d: x = %d, y = %d, time = %d, relative speed = %.4f, beeline speed = %d",
997                     i, (*sampledInputXs)[i], (*sampledInputYs)[i], (*sampledTimes)[i],
998                     (*sampledSpeedRates)[i], (*sampledBeelineSpeedPercentiles)[i]);
999         }
1000         sampledX << (*sampledInputXs)[i];
1001         sampledY << (*sampledInputYs)[i];
1002         if (i != sampledInputSize - 1) {
1003             sampledX << ";";
1004             sampledY << ";";
1005         }
1006     }
1007     AKLOGI("original points:\n%s, %s,\nsampled points:\n%s, %s,\n",
1008             originalX.str().c_str(), originalY.str().c_str(), sampledX.str().c_str(),
1009             sampledY.str().c_str());
1010 }
1011 } // namespace latinime
1012