Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export class ContextToken {
* Contains all relevant correction-search data for use in generating
* corrections for this ContextToken instance.
*/
readonly searchSpace: SearchQuotientSpur;
public get searchSpace(): SearchQuotientSpur {
return this._searchSpace;
}
private _searchSpace: SearchQuotientSpur;

isPartial: boolean;

Expand Down Expand Up @@ -104,7 +107,7 @@ export class ContextToken {
//
// In case we are unable to perfectly track context (say, due to multitaps)
// we need to ensure that only fully-utilized keystrokes are considered.
this.searchSpace = new SearchQuotientSpur(priorToken.searchSpace);
this._searchSpace = priorToken.searchSpace;
this._inputRange = priorToken._inputRange.slice();

// Preserve any annotated applied-suggestion transition ID data; it's useful
Expand All @@ -118,22 +121,24 @@ export class ContextToken {
// May be altered outside of the constructor.
this.isWhitespace = false;
this.isPartial = !!isPartial;
this.searchSpace = new SearchQuotientSpur(model);
this._inputRange = [];

rawText ||= '';

// Supports the old pathway for: updateWithBackspace(tokenText: string, transformId: number)
// Build a token that represents the current text with no ambiguity - probability at max (1.0)
let searchSpace = new SearchQuotientSpur(model);
const BASE_PROBABILITY = 1;
textToCharTransforms(rawText).forEach((transform) => {
this._inputRange.push({
trueTransform: transform,
inputStartIndex: 0,
bestProbFromSet: BASE_PROBABILITY
});
this.searchSpace.addInput([{sample: transform, p: BASE_PROBABILITY}], 1);
searchSpace = searchSpace.addInput([{sample: transform, p: BASE_PROBABILITY}], 1);
});

this._searchSpace = searchSpace;
}
}

Expand All @@ -143,7 +148,7 @@ export class ContextToken {
*/
addInput(inputSource: TokenInputSource, distribution: Distribution<Transform>) {
this._inputRange.push(inputSource);
this.searchSpace.addInput(distribution, inputSource.bestProbFromSet);
this._searchSpace = this._searchSpace.addInput(distribution, inputSource.bestProbFromSet);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import Transform = LexicalModelTypes.Transform;

type RealizedInput = ProbabilityMass<Transform>[]; // NOT Distribution - they're masses from separate distributions.

// p = 1 / (e^4) = 0.01831563888. This still exceeds many neighboring keys!
// p = 1 / (e^5) = 0.00673794699. Strikes a good balance.
// Should easily give priority to neighboring keys before edit-distance kicks in (when keys are a bit ambiguous)
export const EDIT_DISTANCE_COST_SCALE = 5;
export const MIN_KEYSTROKE_PROBABILITY = 0.0001;

export type TraversableToken<TUnit> = {
key: TUnit,
traversal: LexiconTraversal
Expand Down Expand Up @@ -180,7 +186,7 @@ export class SearchNode {
if(this._inputCost !== undefined) {
return this._inputCost;
} else {
let MIN_P = SearchQuotientSpur.MIN_KEYSTROKE_PROBABILITY;
let MIN_P = MIN_KEYSTROKE_PROBABILITY;
// Should technically re-normalize the sampling distribution.
// -ln(p) is smaller for larger probabilities, as ln(p) is always <= 0. Approaches infinity as p => 0.

Expand Down Expand Up @@ -214,7 +220,7 @@ export class SearchNode {
// p = 1 / (e^4) = 0.01831563888. This still exceeds many neighboring keys!
// p = 1 / (e^5) = 0.00673794699. Strikes a good balance.
// Should easily give priority to neighboring keys before edit-distance kicks in (when keys are a bit ambiguous)
return SearchQuotientSpur.EDIT_DISTANCE_COST_SCALE * this.editCount + this.inputSamplingCost;
return EDIT_DISTANCE_COST_SCALE * this.editCount + this.inputSamplingCost;
}

/**
Expand Down Expand Up @@ -667,19 +673,16 @@ export async function *getBestMatches(searchSpace: SearchQuotientSpur, timer: Ex
return null;
}

const entry = newResult.finalNode;

// As we can't guarantee a monotonically-increasing cost during the search -
// due to effects from keystrokes with deleteLeft > 0 - it's technically
// possible to find a lower-cost path later in such cases.
//
// If it occurs, we should re-emit it - it'll show up earlier in the
// suggestions that way, as it should.
if((currentReturns[entry.resultKey]?.currentCost ?? Number.MAX_VALUE) > entry.currentCost) {
currentReturns[entry.resultKey] = entry;
searchSpace.returnedValues[entry.resultKey] = entry;
if((currentReturns[node.resultKey]?.currentCost ?? Number.MAX_VALUE) > node.currentCost) {
currentReturns[node.resultKey] = node;
// Do not track yielded time.
return new SearchResult(entry);
return new SearchResult(node);
}
}

Expand All @@ -695,7 +698,7 @@ export async function *getBestMatches(searchSpace: SearchQuotientSpur, timer: Ex
if(timer.timeSinceLastDefer > STANDARD_TIME_BETWEEN_DEFERS) {
await timer.defer();
}
} while(!timer.elapsed && searchSpace.hasNextMatchEntry());
} while(!timer.elapsed && searchSpace.currentCost < Number.POSITIVE_INFINITY);

return null;
}
Loading