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 @@ -628,42 +628,43 @@ export class SearchResult {
}
}

// Current best guesstimate of how compositor will retrieve ideal corrections.
export async function *getBestMatches(searchSpace: SearchQuotientNode, timer: ExecutionTimer): AsyncGenerator<SearchResult> {
let currentReturns: {[resultKey: string]: SearchNode} = {};
/**
* Searches for the best available corrections from among the provided
* SearchSpaces, ending after the configured timer has elapsed or all available
* corrections have been enumerated.
* @param searchModules
* @param timer
* @returns
*/
export async function *getBestMatches(searchModules: SearchQuotientNode[], timer: ExecutionTimer): AsyncGenerator<SearchResult> {
const spaceQueue = new PriorityQueue<SearchQuotientNode>((a, b) => a.currentCost - b.currentCost);

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

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

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

if(entryFromCache) {
// Time yielded here is generally spent on turning corrections into predictions.
// It's timing a different sort of task, so... different task set ID.
const timeSpan = timer.start(TimedTaskTypes.PREDICTING);
yield entryFromCache;
timeSpan.end();

if(timer.timeSinceLastDefer > STANDARD_TIME_BETWEEN_DEFERS) {
await timer.defer();
}
}
}
}
// Stage 1 - if we already have extracted results, build a queue just for them
// and iterate over it first.
//
// Does not get any results that another iterator pulls up after this is
// created - and those results won't come up later in stage 2, either. Only
// intended for restarting a search, not searching twice in parallel.
const priorResultsQueue = new PriorityQueue<SearchResult>((a, b) => a.totalCost - b.totalCost);
priorResultsQueue.enqueueAll(searchModules.map((space) => space.previousResults).flat());

// With potential prior results re-queued, NOW enqueue. (Not before - the heap may reheapify!)
spaceQueue.enqueueAll(searchModules);

let currentReturns: {[resultKey: string]: SearchNode} = {};

// Stage 2: the fun part; actually searching!
do {
const entry = timer.time(() => {
let newResult: PathResult = searchSpace.handleNextNode();
const entry: SearchResult = timer.time(() => {
if((priorResultsQueue.peek()?.totalCost ?? Number.POSITIVE_INFINITY) < spaceQueue.peek().currentCost) {
const result = priorResultsQueue.dequeue();
currentReturns[result.node.resultKey] = result.node;
return result;
}

let lowestCostSource = spaceQueue.dequeue();
let newResult: PathResult = lowestCostSource.handleNextNode();
spaceQueue.enqueue(lowestCostSource);

if(newResult.type == 'none') {
return null;
Expand Down Expand Up @@ -703,7 +704,7 @@ export async function *getBestMatches(searchSpace: SearchQuotientNode, timer: Ex
if(timer.timeSinceLastDefer > STANDARD_TIME_BETWEEN_DEFERS) {
await timer.defer();
}
} while(!timer.elapsed && searchSpace.currentCost < Number.POSITIVE_INFINITY);
} while(!timer.elapsed && spaceQueue.peek().currentCost < Number.POSITIVE_INFINITY);

return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ContextTransition } from './correction/context-transition.js';
import { ExecutionTimer } from './correction/execution-timer.js';
import ModelCompositor from './model-compositor.js';
import { getBestMatches } from './correction/distance-modeler.js';
import { SearchQuotientSpur } from './correction/search-quotient-spur.js';

const searchForProperty = defaultWordbreaker.searchForProperty;

Expand Down Expand Up @@ -496,7 +495,7 @@ export async function correctAndEnumerate(
let rawPredictions: CorrectionPredictionTuple[] = [];
let bestCorrectionCost: number;
const correctionPredictionMap: Record<string, Distribution<Suggestion>> = {};
for await(const match of getBestMatches(searchModules[0] as SearchQuotientSpur, timer)) {
for await(const match of getBestMatches(searchModules, timer)) {
// Corrections obtained: now to predict from them!
const correction = match.matchString;
const searchSpace = searchModules.find(s => s.spaceId == match.spaceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('ContextToken', function() {
assert.isFalse(token.isWhitespace);

// While searchSpace has no inputs, it _can_ match lexicon entries (via insertions).
let searchIterator = getBestMatches(token.searchModule, new ExecutionTimer(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY));
let searchIterator = getBestMatches([token.searchModule], new ExecutionTimer(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY));
let firstEntry = await searchIterator.next();
assert.isFalse(firstEntry.done);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('getBestMatches', () => {

const searchSpace = new LegacyQuotientRoot(testModel);
const timer = buildTestTimer();
const iter = getBestMatches(searchSpace, timer);
const iter = getBestMatches([searchSpace], timer);

// While there's no input, insertion operations can produce suggestions.
const resultState = await iter.next();
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('getBestMatches', () => {
assert.notEqual(searchPath2.spaceId, searchPath1.spaceId);
assert.notEqual(searchPath3.spaceId, searchPath2.spaceId);

const iter = getBestMatches(searchPath3, buildTestTimer()); // disables the correction-search timeout.
const iter = getBestMatches([searchPath3], buildTestTimer()); // disables the correction-search timeout.
await checkRepeatableResults_teh(iter);
});

Expand Down Expand Up @@ -188,12 +188,12 @@ describe('getBestMatches', () => {
assert.notEqual(searchPath2.spaceId, searchPath1.spaceId);
assert.notEqual(searchPath3.spaceId, searchPath2.spaceId);

const iter = getBestMatches(searchPath3, buildTestTimer()); // disables the correction-search timeout.
const iter = getBestMatches([searchPath3], buildTestTimer()); // disables the correction-search timeout.
await checkRepeatableResults_teh(iter);

// The key: do we get the same results the second time?
// Reset the iterator first...
const iter2 = getBestMatches(searchPath3, buildTestTimer()); // disables the correction-search timeout.
const iter2 = getBestMatches([searchPath3], buildTestTimer()); // disables the correction-search timeout.
await checkRepeatableResults_teh(iter2);
});
});