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