Skip to content
Draft
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
23 changes: 16 additions & 7 deletions web/src/engine/common/web-utils/src/priority-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default class PriorityQueue<Type> {
this.comparator = comparator;

this.heap = (initialEntries ?? []).slice(0);
this.heapify();
this._heapify();
}

private static leftChildIndex(index: number): number {
Expand All @@ -67,13 +67,22 @@ export default class PriorityQueue<Type> {
* are properly satisfied.
* - O(N) when 'heapifying' the whole heap
* - O(N) worst-case for partial heap operations (as part of an enqueueAll)
* <p>
*/
private heapify(): void;
private heapify(start: number, end: number): void;
private heapify(start?: number, end?: number): void {
public heapify(): void {
this._heapify();
}

/**
* Maintains internal state, rearranging the internal state until all heap constraints
* are properly satisfied.
* - O(N) when 'heapifying' the whole heap
* - O(N) worst-case for partial heap operations (as part of an enqueueAll)
*/
private _heapify(): void;
private _heapify(start: number, end: number): void;
private _heapify(start?: number, end?: number): void {
if(start == undefined || end == undefined) {
this.heapify(0, this.count - 1);
this._heapify(0, this.count - 1);
return;
}

Expand Down Expand Up @@ -161,7 +170,7 @@ export default class PriorityQueue<Type> {
const firstParent = PriorityQueue.parentIndex(firstIndex);

// The 'parent' of index 0 will return -1, which is illegal.
this.heapify(firstParent >= 0 ? firstParent : 0, PriorityQueue.parentIndex(this.count-1));
this._heapify(firstParent >= 0 ? firstParent : 0, PriorityQueue.parentIndex(this.count-1));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ export async function *getBestMatches(searchSpaces: SearchSpace[], timer: Execut
let bestQueue = spaceQueue.dequeue();
const newResult = bestQueue.handleNextNode();
spaceQueue.enqueue(bestQueue);
spaceQueue.heapify();

if(newResult.type == 'none') {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,21 @@ export class SearchCluster implements SearchSpace {
private selectionQueue: PriorityQueue<SearchPath> = new PriorityQueue(PATH_QUEUE_COMPARATOR);
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.
* Holds all `incomingNode` child buffers - buffers to hold nodes processed by
* this SearchCluster but not yet by child SearchSpaces.
*/
private childBuffers: SearchNode[][] = [];

// 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.
* 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[] = [];

Expand Down Expand Up @@ -150,8 +157,10 @@ export class SearchCluster implements SearchSpace {
const bestPath = this.selectionQueue.dequeue();
const currentResult = bestPath.handleNextNode();
this.selectionQueue.enqueue(bestPath);
this.selectionQueue.heapify();

if(currentResult.type == 'complete') {
this.bufferNode(currentResult.finalNode);
this.completedPaths?.push(currentResult.finalNode);
currentResult.spaceId = this.spaceId;
}
Expand All @@ -163,8 +172,12 @@ export class SearchCluster implements SearchSpace {
return this.completedPaths?.map((n => new SearchResult(n, this.spaceId))) ?? [];
}

public stopTrackingResults() {
delete this.completedPaths;
public addResultBuffer(nodeBuffer: SearchNode[]): void {
this.childBuffers.push(nodeBuffer);
}

private bufferNode(node: SearchNode) {
this.childBuffers.forEach((buf) => buf.push(node));
}

get model(): LexicalModelTypes.LexicalModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ export const QUEUE_NODE_COMPARATOR: Comparator<SearchNode> = function(arg1, arg2
// Whenever a wordbreak boundary is crossed, a new instance should be made.
export class SearchPath implements SearchSpace {
private selectionQueue: PriorityQueue<SearchNode> = new PriorityQueue(QUEUE_NODE_COMPARATOR);

/**
* Holds all incoming Nodes generated from a parent `SearchSpace` that have not yet been
* extended with this `SearchSpace`'s input.
*/
private incomingNodes: SearchNode[] = [];

/**
* Holds all `incomingNode` child buffers - buffers to hold nodes processed by
* this SearchPath but not yet by child SearchSpaces.
*/
private childBuffers: SearchNode[][] = [];

readonly inputs?: Distribution<Transform>;
readonly inputSource?: PathInputProperties;

Expand Down Expand Up @@ -135,6 +148,7 @@ export class SearchPath implements SearchSpace {
this.codepointLength = baseLength + this.edgeLength - deleteLeft;

this.addEdgesForNodes(parentSpace.previousResults.map(r => r.node));
parentSpace.addResultBuffer(this.incomingNodes);

return;
}
Expand Down Expand Up @@ -386,6 +400,14 @@ export class SearchPath implements SearchSpace {
}

public get currentCost(): number {
if(this.incomingNodes.length > 0) {
this.addEdgesForNodes(this.incomingNodes);

// Preserve the array instance, but trash all entries.
// The array is registered with the parent; do not replace!
this.incomingNodes.splice(0, this.incomingNodes.length);
}

const parentCost = this.parentSpace?.currentCost ?? Number.POSITIVE_INFINITY;
const localCost = this.selectionQueue.peek()?.currentCost ?? Number.POSITIVE_INFINITY;

Expand Down Expand Up @@ -416,17 +438,31 @@ export class SearchPath implements SearchSpace {
* @returns
*/
public handleNextNode(): PathResult {
if(this.incomingNodes.length > 0) {
this.addEdgesForNodes(this.incomingNodes);

// Preserve the array instance, but trash all entries.
// The array is registered with the parent; do not replace!
this.incomingNodes.splice(0, this.incomingNodes.length);
}

const parentCost = this.parentSpace?.currentCost ?? Number.POSITIVE_INFINITY;
const localCost = this.selectionQueue.peek()?.currentCost ?? Number.POSITIVE_INFINITY;

if(parentCost <= localCost) {
if(parentCost < localCost) {
if(parentCost == Number.POSITIVE_INFINITY) {
return {
type: 'none'
};
}

const result = this.parentSpace.handleNextNode();
// The parent will insert the node into our queue. We don't need it, though
// any siblings certainly will.

// Preserve the array instance, but trash all entries.
// The array is registered with the parent; do not replace!
this.incomingNodes.splice(0, this.incomingNodes.length);

if(result.type == 'complete') {
this.addEdgesForNodes([result.finalNode]);
Expand Down Expand Up @@ -492,6 +528,7 @@ export class SearchPath implements SearchSpace {
}
}

this.bufferNode(currentNode);
return {
type: 'complete',
cost: currentNode.currentCost,
Expand All @@ -508,6 +545,14 @@ export class SearchPath implements SearchSpace {
return Object.values(this.returnedValues ?? {}).map(v => new SearchResult(v));
}

public addResultBuffer(nodeBuffer: SearchNode[]): void {
this.childBuffers.push(nodeBuffer);
}

private bufferNode(node: SearchNode) {
this.childBuffers.forEach((buf) => buf.push(node));
}

public get inputSegments(): InputSegment[] {
if(!this.parentSpace) {
return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,12 @@ export interface SearchSpace {
readonly constituentPaths: SearchPath[][];

isSameSpace(space: SearchSpace): boolean;

/**
* This is used among SearchSpaces to ensure that nodes processed by earlier portions
* of the correction-search dynamic graph are provided to all child SearchSpaces for
* construction of new portions of the graph corresponding to their modeled inputs.
* @param nodeBuffer
*/
addResultBuffer(nodeBuffer: SearchNode[]): void;
}
Loading