From e4845fdc650c505f86409f75fdaf856d1affd7d1 Mon Sep 17 00:00:00 2001 From: maia arson crimew Date: Fri, 5 Aug 2022 20:48:26 +0200 Subject: [PATCH] [#8] more work on proximity info state --- native/rust/src/defines.rs | 4 + .../core/layout/proximity_info_params.rs | 37 ++++ .../core/layout/proximity_info_state.rs | 204 +++++++++++++++++- 3 files changed, 235 insertions(+), 10 deletions(-) diff --git a/native/rust/src/defines.rs b/native/rust/src/defines.rs index 73c965f14..554caf583 100644 --- a/native/rust/src/defines.rs +++ b/native/rust/src/defines.rs @@ -8,6 +8,10 @@ pub const KEYCODE_SPACE: char = ' '; pub const KEYCODE_SINGLE_QUOTE: char = '\''; pub const KEYCODE_HYPHEN_MINUS: char = '-'; +pub const MAX_PERCENTILE: i32 = 100; + +pub const MAX_VALUE_FOR_WEIGHTING: i32 = 10000000; + pub enum DoubleLetterLevel { None, DoubleLetter, diff --git a/native/rust/src/suggest/core/layout/proximity_info_params.rs b/native/rust/src/suggest/core/layout/proximity_info_params.rs index 07b6223c1..aeb543b86 100644 --- a/native/rust/src/suggest/core/layout/proximity_info_params.rs +++ b/native/rust/src/suggest/core/layout/proximity_info_params.rs @@ -22,4 +22,41 @@ pub const NUM_POINTS_FOR_SPEED_CALCULATION: usize = 2; pub const LAST_POINT_SKIP_DISTANCE_SCALE: i32 = 4; +pub const LOOKUP_RADIUS_PERCENTILE: i32 = 50; +pub const FIRST_POINT_TIME_OFFSET_MILLIS: i32 = 150; +pub const STRONG_DOUBLE_LETTER_TIME_MILLIS: i32 = 600; + +pub const MIN_PROBABILITY: f32 = 0.000005; +pub const MAX_SKIP_PROBABILITY: f32 = 0.95; +pub const SKIP_FIRST_POINT_PROBABILITY: f32 = 0.01; +pub const SKIP_LAST_POINT_PROBABILITY: f32 = 0.1; +pub const MIN_SPEED_RATE_FOR_SKIP_PROBABILITY: f32 = 0.15; +pub const SPEED_WEIGHT_FOR_SKIP_PROBABILITY: f32 = 0.9; +pub const SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY: f32 = 0.6; +pub const NEAREST_DISTANCE_WEIGHT: f32 = 0.5; +pub const NEAREST_DISTANCE_BIAS: f32 = 0.5; +pub const NEAREST_DISTANCE_WEIGHT_FOR_LAST: f32 = 0.6; +pub const NEAREST_DISTANCE_BIAS_FOR_LAST: f32 = 0.4; +pub const ANGLE_WEIGHT: f32 = 0.90; +pub const DEEP_CORNER_ANGLE_THRESHOLD: f32 = PI * 60.0 / 180.0; +pub const SKIP_DEEP_CORNER_PROBABILITY: f32 = 0.1; +pub const CORNER_ANGLE_THRESHOLD: f32 = PI * 30.0 / 180.0; +pub const STRAIGHT_ANGLE_THRESHOLD: f32 = PI * 15.0 / 180.0; +pub const SKIP_CORNER_PROBABILITY: f32 = 0.4; +pub const SPEED_MARGIN: f32 = 0.1; +pub const CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION: f32 = 0.0; +// TODO: The variance is critical for accuracy; thus, adjusting these parameters by machine +// learning or something would be efficient. +pub const SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION: f32 = 0.3; +pub const MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION: f32 = 0.25; +pub const SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION: f32 = 0.5; +pub const MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION: f32 = 0.15; +pub const MIN_STANDARD_DEVIATION: f32 = 0.37; +pub const STANDARD_DEVIATION_X_WEIGHT_FOR_FIRST: f32 = 1.25; +pub const STANDARD_DEVIATION_Y_WEIGHT_FOR_FIRST: f32 = 0.85; +pub const STANDARD_DEVIATION_X_WEIGHT_FOR_LAST: f32 = 1.4; +pub const STANDARD_DEVIATION_Y_WEIGHT_FOR_LAST: f32 = 0.95; +pub const STANDARD_DEVIATION_X_WEIGHT: f32 = 1.1; +pub const STANDARD_DEVIATION_Y_WEIGHT: f32 = 0.95; + // TODO: add the rest when we get to them diff --git a/native/rust/src/suggest/core/layout/proximity_info_state.rs b/native/rust/src/suggest/core/layout/proximity_info_state.rs index ec2b4471b..06be270f8 100644 --- a/native/rust/src/suggest/core/layout/proximity_info_state.rs +++ b/native/rust/src/suggest/core/layout/proximity_info_state.rs @@ -1,18 +1,19 @@ -use std::cmp::min; +use std::cmp::{max, min}; use std::collections::HashMap; use indexmap::IndexMap; -use crate::defines::DoubleLetterLevel; +use crate::defines::{DoubleLetterLevel, MAX_PERCENTILE, MAX_VALUE_FOR_WEIGHTING}; use crate::math::{get_angle, get_angle_diff, get_distance_int}; use crate::suggest::core::layout::proximity_info::ProximityInfo; use crate::suggest::core::layout::proximity_info_params::{ CORNER_ANGLE_THRESHOLD_FOR_POINT_SCORE, CORNER_CHECK_DISTANCE_THRESHOLD_SCALE, CORNER_SCORE, - CORNER_SUM_ANGLE_THRESHOLD, DISTANCE_BASE_SCALE, LAST_POINT_SKIP_DISTANCE_SCALE, - LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE, MARGIN_FOR_PREV_LOCAL_MIN, + CORNER_SUM_ANGLE_THRESHOLD, DISTANCE_BASE_SCALE, FIRST_POINT_TIME_OFFSET_MILLIS, + LAST_POINT_SKIP_DISTANCE_SCALE, LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE, + LOOKUP_RADIUS_PERCENTILE, MARGIN_FOR_PREV_LOCAL_MIN, MAX_SKIP_PROBABILITY, MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE, NEAR_KEY_THRESHOLD_FOR_DISTANCE, NEAR_KEY_THRESHOLD_FOR_POINT_SCORE, NOT_LOCALMIN_DISTANCE_SCORE, - NUM_POINTS_FOR_SPEED_CALCULATION, + NUM_POINTS_FOR_SPEED_CALCULATION, STRONG_DOUBLE_LETTER_TIME_MILLIS, }; type NearKeysDistanceMap = IndexMap; @@ -123,8 +124,8 @@ impl ProximityInfoState { // TODO: we assume that we always have x coordinates and y coordinates, the cpp code doesnt self.sampled_input_size = self.update_touch_points( - x_coordinates, - y_coordinates, + &x_coordinates, + &y_coordinates, ×, pointer_ids, input_size, @@ -140,7 +141,15 @@ impl ProximityInfoState { &y_coordinates, ×, last_saved_input_size, - ) + ); + self.refresh_beeline_speed_rates(input_size, &x_coordinates, &y_coordinates, ×); + } + + if self.sampled_input_size > 0 { + self.init_geometric_distance_infos(last_saved_input_size, is_geometric); + if is_geometric { + // updates probabilities of skipping or mapping each key for all points + } } } @@ -330,8 +339,8 @@ impl ProximityInfoState { fn update_touch_points( &mut self, - x_coordinates: Vec, - y_coordinates: Vec, + x_coordinates: &Vec, + y_coordinates: &Vec, times: &Option>, // TODO: this appears to be assumed nullable in cpp, for now we require it pointer_ids: Vec, @@ -711,6 +720,181 @@ impl ProximityInfoState { self.speed_rates[i] = speed / average_speed; } } + + // Direction calculation. + self.directions.resize(self.sampled_input_size - 1, 0.0); + for i in max(0, last_saved_input_size - 1)..self.sampled_input_size - 1 { + self.directions[i] = self.get_direction_between(i, i + 1); + } average_speed } + + fn refresh_beeline_speed_rates( + &mut self, + input_size: usize, + x_coordinates: &Vec, + y_coordinates: &Vec, + times: &Option>, + ) { + self.beeline_speed_percentiles + .resize(self.sampled_input_size, 0); + for i in 0..self.sampled_input_size { + self.beeline_speed_percentiles[i] = (self.calculate_beeline_speed_rate( + i, + input_size, + x_coordinates, + y_coordinates, + times, + ) * MAX_PERCENTILE as f32) as i32 + } + } + + fn calculate_beeline_speed_rate( + &self, + id: usize, + input_size: usize, + x_coordinates: &Vec, + y_coordinates: &Vec, + times: &Option>, + ) -> f32 { + if self.sampled_input_size == 0 || self.average_speed < 0.001 { + // invalid state + return 1.0; + } + + let lookup_radius = self.proximity_info.get_most_common_key_width() + * LOOKUP_RADIUS_PERCENTILE + / MAX_PERCENTILE; + let x0 = self.sampled_input_xs[id]; + let y0 = self.sampled_input_ys[id]; + let actual_input_index = self.sampled_input_indice[id]; + + // let mut temp_time = 0; + let mut temp_beeline_distance = 0; + let mut start = actual_input_index; + + // lookup forward + while start > 0 && temp_beeline_distance < lookup_radius { + // temp_time += times[start] - times[start - 1] + start -= 1; + temp_beeline_distance = + get_distance_int(x0, y0, x_coordinates[start], y_coordinates[start]); + } + // Exclusive unless this is an edge point + if start > 0 && start < actual_input_index { + start += 1; + } + // temp_time = 0; + temp_beeline_distance = 0; + let mut end = actual_input_index; + // lookup backward + while end < input_size - 1 && temp_beeline_distance < lookup_radius { + // temp_time += times[end + 1] - times[end] + end += 1; + temp_beeline_distance = + get_distance_int(x0, y0, x_coordinates[end], y_coordinates[end]); + } + // Exclusive unless this is an edge point + if end > actual_input_index && end < input_size - 1 { + end -= 1; + } + + if start >= end { + // double letter start == end + return 1.0; + } + + let x_start = x_coordinates[start]; + let y_start = y_coordinates[start]; + let x_end = x_coordinates[end]; + let y_end = y_coordinates[end]; + + let beeline_distance = get_distance_int(x_start, y_start, x_end, y_end); + let (mut adjusted_start_time, mut adjusted_end_time) = if let Some(times) = times { + (times[start], times[end]) + } else { + return 1.0; + }; + if start == 0 && actual_input_index == 0 && input_size > 1 { + adjusted_start_time += FIRST_POINT_TIME_OFFSET_MILLIS; + } + if end == input_size - 1 && input_size > 1 { + adjusted_end_time -= FIRST_POINT_TIME_OFFSET_MILLIS; + } + let time = adjusted_end_time - adjusted_start_time; + + if time >= STRONG_DOUBLE_LETTER_TIME_MILLIS { + return 0.0; + } + + // Offset 1% + // TODO: Detect double letter more smartly + 0.01 + beeline_distance as f32 / time as f32 / self.average_speed + } + + fn init_geometric_distance_infos(&mut self, last_saved_input_size: usize, is_geometric: bool) { + let key_count = self.proximity_info.get_key_count(); + self.sampled_normalized_squared_length_cache + .resize(self.sampled_input_size * key_count, 0.0); + for i in last_saved_input_size..self.sampled_input_size { + for k in 0..key_count { + let index = i * key_count + k; + let x = self.sampled_input_xs[i]; + let y = self.sampled_input_ys[i]; + self.sampled_normalized_squared_length_cache[index] = self + .proximity_info + .get_normalized_square_distance_from_center_float_g(k, x, y, is_geometric); + } + } + } + + // Updates probabilities of aligning to some keys and skipping. + // Word suggestion should be based on this probabilities. + fn update_align_point_probabilities(&mut self, last_saved_input_size: usize) { + self.char_probabilities + .resize(self.sampled_input_size, HashMap::default()); + // Calculates probabilities of using a point as a correlated point with the character + // for each point. + for i in last_saved_input_size..self.sampled_input_size { + self.char_probabilities[i].clear(); + // First, calculates skip probability. Starts from MAX_SKIP_PROBABILITY. + // Note that all values that are multiplied to this probability should be in [0.0, 1.0]; + let mut skip_probability = MAX_SKIP_PROBABILITY; + + let current_angle = self.get_point_angle(i); + let speed_rate = self.speed_rates[i]; + + let mut nearest_key_distance = MAX_VALUE_FOR_WEIGHTING as f32; + for j in 0..self.proximity_info.get_key_count() { + //let distance = self.getpointto + } + } + } + + fn get_point_angle(&self, index: usize) -> f32 { + if index == 0 || index >= self.sampled_input_xs.len() - 1 { + return 0.0; + } + let previous_direction = self.get_direction_between(index - 1, index); + let next_direction = self.get_direction_between(index, index + 1); + get_angle_diff(previous_direction, next_direction) + } + + fn get_direction_between(&self, index0: usize, index1: usize) -> f32 { + let sampled_input_size = self.sampled_input_xs.len(); + if index0 == 0 + || index0 > sampled_input_size - 1 + || index1 == 0 + || index1 > sampled_input_size - 1 + { + return 0.0; + } + + get_angle( + self.sampled_input_xs[index0], + self.sampled_input_ys[index0], + self.sampled_input_xs[index1], + self.sampled_input_ys[index1], + ) + } }