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 @@ -5,8 +5,7 @@ import { LexicalModelTypes } from '@keymanapp/common-types';

import { ClassicalDistanceCalculation } from './classical-calculation.js';
import { ExecutionTimer, STANDARD_TIME_BETWEEN_DEFERS } from './execution-timer.js';
import { QUEUE_NODE_COMPARATOR, SearchQuotientSpur } from './search-quotient-spur.js';
import { PathResult } from './search-quotient-node.js';
import { PathResult, SearchQuotientNode } from './search-quotient-node.js';
import { subsetByChar, subsetByInterval, mergeSubset, TransformSubset } from '../transform-subsets.js';
import TransformUtils from '../transformUtils.js';

Expand Down Expand Up @@ -571,22 +570,22 @@ export class SearchNode {
}

export class SearchResult {
private resultNode: SearchNode;
readonly node: SearchNode;

constructor(node: SearchNode) {
this.resultNode = node;
this.node = node;
}

get inputSequence(): ProbabilityMass<Transform>[] {
return this.resultNode.priorInput;
return this.node.priorInput;
}

get matchSequence(): TraversableToken<string>[] {
return this.resultNode.calculation.matchSequence.map((char, i) => ({key: char, traversal: this.resultNode.matchedTraversals[i+1]}));
return this.node.calculation.matchSequence.map((char, i) => ({key: char, traversal: this.node.matchedTraversals[i+1]}));
};

get matchString(): string {
return this.resultNode.resultKey;
return this.node.resultKey;
}

/**
Expand All @@ -597,15 +596,15 @@ export class SearchResult {
* `totalCost`.)
*/
get knownCost(): number {
return this.resultNode.editCount;
return this.node.editCount;
}

/**
* Gets the "input sampling cost" of the edge, which should be considered as the
* negative log-likelihood of the input path taken to reach the node.
*/
get inputSamplingCost(): number {
return this.resultNode.inputSamplingCost;
return this.node.inputSamplingCost;
}

/**
Expand All @@ -615,41 +614,34 @@ export class SearchResult {
* to the resulting output.
*/
get totalCost(): number {
return this.resultNode.currentCost;
return this.node.currentCost;
}

get finalTraversal(): LexiconTraversal {
return this.resultNode.currentTraversal;
return this.node.currentTraversal;
}

get spaceId(): number {
return this.resultNode.spaceId;
return this.node.spaceId;
}
}

// Current best guesstimate of how compositor will retrieve ideal corrections.
export async function *getBestMatches(searchSpace: SearchQuotientSpur, timer: ExecutionTimer): AsyncGenerator<SearchResult> {
export async function *getBestMatches(searchSpace: SearchQuotientNode, timer: ExecutionTimer): AsyncGenerator<SearchResult> {
let currentReturns: {[resultKey: string]: SearchNode} = {};

// Stage 1 - if we already have extracted results, build a queue just for them and iterate over it first.
const returnedValues = Object.values(searchSpace.returnedValues);
const returnedValues = Object.values(searchSpace.previousResults);
if(returnedValues.length > 0) {
let preprocessedQueue = new PriorityQueue<SearchNode>(QUEUE_NODE_COMPARATOR, returnedValues);
let preprocessedQueue = new PriorityQueue<SearchResult>((a, b) => a.totalCost - b.totalCost, returnedValues);

while(preprocessedQueue.count > 0) {
const entryFromCache = timer.time(() => {
let entry = preprocessedQueue.dequeue();

// Is the entry a reasonable result?
if(entry.isFullReplacement) {
// If the entry's 'match' fully replaces the input string, we consider it
// unreasonable and ignore it.
return null;
}

currentReturns[entry.resultKey] = entry;
currentReturns[entry.node.resultKey] = entry.node;
// Do not track yielded time.
return new SearchResult(entry);
return entry;
}, TimedTaskTypes.CACHED_RESULT);

if(entryFromCache) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,11 @@ export class SearchQuotientSpur implements SearchQuotientNode {
private parentPath: SearchQuotientSpur;
readonly spaceId: number;

// We use an array and not a PriorityQueue b/c batch-heapifying at a single point in time
// is cheaper than iteratively building a priority queue.
/**
* This tracks all paths that have reached the end of a viable input-matching path - even
* those of lower cost that produce the same correction as other paths.
*
* When new input is received, its entries are then used to append edges to the path in order
* to find potential paths to reach a new viable end.
*/
private completedPaths?: SearchNode[] = [];

/**
* Marks all results that have already been returned since the last input was received.
* Is cleared after .addInput() calls.
*/
public returnedValues?: {[resultKey: string]: SearchNode} = {}; // TODO: make it private again!
private returnedValues?: {[resultKey: string]: SearchNode} = {};

/**
* Provides a heuristic for the base cost at each depth if the best
Expand All @@ -75,15 +64,14 @@ export class SearchQuotientSpur implements SearchQuotientNode {
this.lowestCostAtDepth = parentNode.lowestCostAtDepth.concat(logTierCost);
this.parentPath = parentNode;

this.addEdgesForNodes(parentNode.completedPaths);
this.addEdgesForNodes(parentNode.previousResults.map(v => v.node));

return;
}

const model = arg1 as LexicalModel;
this.selectionQueue.enqueue(new SearchNode(model.traverseFromRoot(), this.spaceId, t => model.toKey(t)));
this.lowestCostAtDepth = [];
this.completedPaths = [];
}

/**
Expand Down Expand Up @@ -236,9 +224,6 @@ export class SearchQuotientSpur implements SearchQuotientNode {
this.selectionQueue.enqueueAll(insertionEdges);
}

// It was the final tier - store the node for future reference.
this.completedPaths?.push(currentNode);

if((this.returnedValues[currentNode.resultKey]?.currentCost ?? Number.POSITIVE_INFINITY) > currentNode.currentCost) {
this.returnedValues[currentNode.resultKey] = currentNode;
} else {
Expand All @@ -255,6 +240,6 @@ export class SearchQuotientSpur implements SearchQuotientNode {
}

public get previousResults(): SearchResult[] {
return Object.values(this.returnedValues).map(v => new SearchResult(v));
return Object.values(this.returnedValues ?? {}).map(v => new SearchResult(v));
}
}