mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-29 19:57:41 +00:00
refactor(web): renames PendingTokenization as TokenizationEdge, adds doc-comments
Now that the SearchQuotientNode rework is stabilizing, I've finally landed on a better name for the type. PendingTokenization _directly_ correlates to outbound edges from `SearchQuotientNode` entries once processed. New doc-comments are also added documenting the relationship of `TransitionEdge` and `TokenizationSubset` to `SearchQuotientSpur` and `SearchQuotientCluster`. Admittedly, it may be wise to additionally rename `TokenizationSubset` to `TransitionCluster` (and rename the source file) to further mirror the relationship of these types... but that can always be done in a follow-up. Build-bot: skip build:web Test-bot: skip
This commit is contained in:
parent
fd45aa4281
commit
cfe72f4aa2
6 changed files with 69 additions and 51 deletions
|
|
@ -243,7 +243,7 @@ export class ContextState {
|
|||
const baseTokenization = startTokenizationsAfterSlide[0];
|
||||
// For multiple tokenizations, we'd retrieve each, use the "most likely" one as base,
|
||||
// and then fold all resulting search spaces (on the final token) into one.
|
||||
const tokenizationAnalysis = trueInputSubset.pendingSet.get(baseTokenization);
|
||||
const tokenizationAnalysis = trueInputSubset.transitionEdges.get(baseTokenization);
|
||||
|
||||
// Determine the best probability from among ALL available inputs, before they're split
|
||||
// into subsets.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import TransformUtils from '../transformUtils.js';
|
|||
import { computeDistance, EditOperation, EditTuple } from './classical-calculation.js';
|
||||
import { determineModelTokenizer } from '../model-helpers.js';
|
||||
import { ExtendedEditOperation, SegmentableDistanceCalculation } from './segmentable-calculation.js';
|
||||
import { PendingTokenization } from './tokenization-subsets.js';
|
||||
import { TransitionEdge } from './tokenization-subsets.js';
|
||||
|
||||
import LexicalModel = LexicalModelTypes.LexicalModel;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
|
|
@ -46,7 +46,7 @@ export interface TokenSplitMap {
|
|||
* by any word-boundary shifts in existing content that occur due incoming
|
||||
* Transform side-effects.
|
||||
*/
|
||||
export interface TokenizationEdgeAlignment {
|
||||
export interface TransitionEdgeAlignment {
|
||||
/**
|
||||
* Denotes any token merge edits needed after applying the Transform.
|
||||
*/
|
||||
|
|
@ -86,7 +86,7 @@ export interface TokenizationTransitionEdits {
|
|||
* implied by the Transform but not generated within new content produced by
|
||||
* its text edits.
|
||||
*/
|
||||
alignment: TokenizationEdgeAlignment;
|
||||
alignment: TransitionEdgeAlignment;
|
||||
|
||||
/**
|
||||
* The tokenized form of the input Transform, indexed by position relative to
|
||||
|
|
@ -108,7 +108,7 @@ export class ContextTokenization {
|
|||
* The tokenization-transition metadata relating this instance to the most likely
|
||||
* tokenization from a prior state.
|
||||
*/
|
||||
readonly transitionEdits?: PendingTokenization;
|
||||
readonly transitionEdits?: TransitionEdge;
|
||||
|
||||
/**
|
||||
* The portion of edits from the true input keystroke that are not part of the
|
||||
|
|
@ -125,10 +125,10 @@ export class ContextTokenization {
|
|||
|
||||
constructor(priorToClone: ContextTokenization);
|
||||
constructor(tokens: ContextToken[]);
|
||||
constructor(tokens: ContextToken[], alignment: PendingTokenization, taillessTrueKeystroke: Transform);
|
||||
constructor(tokens: ContextToken[], alignment: TransitionEdge, taillessTrueKeystroke: Transform);
|
||||
constructor(
|
||||
param1: ContextToken[] | ContextTokenization,
|
||||
alignment?: PendingTokenization,
|
||||
alignment?: TransitionEdge,
|
||||
taillessTrueKeystroke?: Transform
|
||||
) {
|
||||
if(!(param1 instanceof ContextTokenization)) {
|
||||
|
|
@ -490,7 +490,7 @@ export class ContextTokenization {
|
|||
* Given results from `precomputeTokenizationAfterInput`, this method will
|
||||
* evaluate the pending transition in tokenization for all associated inputs
|
||||
* while reusing as many correction-search intermediate results as possible.
|
||||
* @param pendingTokenization Batched results from one or more
|
||||
* @param transitionEdge Batched results from one or more
|
||||
* `precomputeTokenizationAfterInput` calls on this instance, all with the
|
||||
* same alignment values.
|
||||
* @param lexicalModel The active lexical model
|
||||
|
|
@ -498,17 +498,17 @@ export class ContextTokenization {
|
|||
* the transition.
|
||||
* @param bestProbFromSet The probability of the single most likely input
|
||||
* transform in the overall transformDistribution associated with the
|
||||
* keystroke triggering the transition. It need not be represented by the
|
||||
* pendingTokenization to be built.
|
||||
* keystroke triggering theh transition. It need not be represented by the
|
||||
* TransitionEdge to be built.
|
||||
* @returns
|
||||
*/
|
||||
evaluateTransition(
|
||||
pendingTokenization: PendingTokenization,
|
||||
transitionEdge: TransitionEdge,
|
||||
lexicalModel: LexicalModel,
|
||||
sourceInput: Transform,
|
||||
bestProbFromSet: number
|
||||
): ContextTokenization {
|
||||
const { alignment: alignment, inputs } = pendingTokenization;
|
||||
const { alignment: alignment, inputs } = transitionEdge;
|
||||
const sliceIndex = alignment.edgeWindow.sliceIndex;
|
||||
const baseTokenization = this.tokens.slice(sliceIndex);
|
||||
let affectedToken: ContextToken;
|
||||
|
|
@ -592,7 +592,7 @@ export class ContextTokenization {
|
|||
start: appliedLength
|
||||
},
|
||||
bestProbFromSet: bestProbFromSet,
|
||||
subsetId: pendingTokenization.inputSubsetId
|
||||
subsetId: transitionEdge.inputSubsetId
|
||||
}, distribution);
|
||||
appliedLength += KMWString.length(distribution[0].sample.insert);
|
||||
|
||||
|
|
@ -605,7 +605,7 @@ export class ContextTokenization {
|
|||
return new ContextTokenization(
|
||||
this.tokens.slice(0, sliceIndex).concat(tokenization),
|
||||
null /* tokenMapping */,
|
||||
determineTaillessTrueKeystroke(pendingTokenization)
|
||||
determineTaillessTrueKeystroke(transitionEdge)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1122,7 +1122,7 @@ export function assembleTransforms(stackedInserts: string[], stackedDeletes: num
|
|||
* @param tokenizationAnalysis
|
||||
* @returns
|
||||
*/
|
||||
export function determineTaillessTrueKeystroke(tokenizationAnalysis: PendingTokenization) {
|
||||
export function determineTaillessTrueKeystroke(tokenizationAnalysis: TransitionEdge) {
|
||||
// undefined by default; we haven't yet determined if we're still affecting
|
||||
// the same token that was the tail in the previous tokenization state.
|
||||
let taillessTrueKeystroke: Transform;
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export interface PathInputProperties {
|
|||
* This tends to serve as an identifying factor for tokenized input distributions,
|
||||
* indicating the distributions were all sourced from the same original input event.
|
||||
*
|
||||
* @see PendingTokenization.inputSubsetId
|
||||
* @see TransitionEdge.inputSubsetId
|
||||
*/
|
||||
subsetId: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { LexicalModelTypes } from '@keymanapp/common-types';
|
|||
import { SENTINEL_CODE_UNIT } from '@keymanapp/models-templates';
|
||||
import { KMWString } from '@keymanapp/web-utils';
|
||||
|
||||
import { ContextTokenization, TokenizationEdgeAlignment, TokenizationTransitionEdits } from './context-tokenization.js';
|
||||
import { ContextTokenization, TransitionEdgeAlignment, TokenizationTransitionEdits } from './context-tokenization.js';
|
||||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
|
|
@ -13,21 +13,29 @@ export function generateSubsetId() {
|
|||
return SUBSET_ID_SEED++;
|
||||
}
|
||||
|
||||
export interface PendingTokenization {
|
||||
/**
|
||||
* Tracks metadata about the input "edge" transitioning from one source
|
||||
* ContextTokenization to a potentially-common destination ContextTokenization.
|
||||
*
|
||||
* Once evaluated, each entry within its `.inputs` field should have a
|
||||
* one-to-one relationship with instances of the `SearchPath` class.
|
||||
*/
|
||||
export interface TransitionEdge {
|
||||
/**
|
||||
* The edge window corresponding to the common tokenization for the subset's inputs
|
||||
* The edge window corresponding to the common ContextTokenization context
|
||||
* to which this path's inputs will be applied.
|
||||
*/
|
||||
alignment: TokenizationEdgeAlignment,
|
||||
alignment: TransitionEdgeAlignment,
|
||||
|
||||
/**
|
||||
* A set of incoming keystrokes with compatible effects when applied.
|
||||
*
|
||||
* If passed to `subsetByInterval`, the transforms should result in a single subset.
|
||||
* If passed to the`subsetByInterval`, the transforms should result in a single subset.
|
||||
*/
|
||||
inputs: Distribution<Map<number, Transform>>
|
||||
|
||||
/**
|
||||
* A unique identifier associated with this PendingTokenization and its
|
||||
* A unique identifier associated with this TransitionEdge and its
|
||||
* transforms within `SearchSpace`s. This ID assists with detecting when
|
||||
* split transforms are re-merged during SearchSpace merges. Only
|
||||
* input-sources with matching subset ID come from the same subset, and thus
|
||||
|
|
@ -42,7 +50,17 @@ export interface PendingTokenization {
|
|||
}
|
||||
|
||||
/**
|
||||
* Defines a subset of pending tokenization transitions based on potential inputs.
|
||||
* Defines a subset of pending tokenization transitions based on potential
|
||||
* inputs.
|
||||
*
|
||||
* If more than one `transitionEdges` entry exists, this should directly
|
||||
* correspond to a unique instance of `SearchCluster` (per affected
|
||||
* `ContextToken`) once fully processed, each comprised of the corresponding
|
||||
* `SearchPath` entries constructed from each `transitionEdges` entry.
|
||||
*
|
||||
* If only one `transitionEdges` entry exists, it should correspond to
|
||||
* `SearchPath` instances instead; there is no need for `SearchCluster` overhead
|
||||
* in such cases.
|
||||
*/
|
||||
export interface TokenizationSubset {
|
||||
/**
|
||||
|
|
@ -55,7 +73,7 @@ export interface TokenizationSubset {
|
|||
* them, yielding compatible search paths and tokenization effects after their
|
||||
* application.
|
||||
*/
|
||||
readonly pendingSet: Map<ContextTokenization, PendingTokenization>;
|
||||
readonly transitionEdges: Map<ContextTokenization, TransitionEdge>;
|
||||
}
|
||||
|
||||
export function editKeyer(precomputation: TokenizationTransitionEdits): string[] {
|
||||
|
|
@ -213,13 +231,13 @@ export class TokenizationSubsetBuilder {
|
|||
// Maps any number of Tokenizations and their incoming alignment data to a common key
|
||||
// for final tokenization forms.
|
||||
const entry: TokenizationSubset = this._subsets.get(key) ?? {
|
||||
pendingSet: new Map(),
|
||||
transitionEdges: new Map(),
|
||||
key: key
|
||||
}
|
||||
|
||||
// Finds any previously-accumulated data corresponding to both the incoming and
|
||||
// target final tokenization form, creating an empty entry if none yet exists.
|
||||
const forTokenization: PendingTokenization = entry.pendingSet.get(tokenization) ?? {
|
||||
const forTokenization: TransitionEdge = entry.transitionEdges.get(tokenization) ?? {
|
||||
alignment: precomputation.alignment,
|
||||
inputs: [],
|
||||
inputSubsetId: generateSubsetId()
|
||||
|
|
@ -228,7 +246,7 @@ export class TokenizationSubsetBuilder {
|
|||
// Adds the incoming tokenized transform data for the pairing...
|
||||
forTokenization.inputs.push({sample: precomputation.tokenizedTransform, p});
|
||||
// and ensures that the pairing's data-accumulator is in the map.
|
||||
entry.pendingSet.set(tokenization, forTokenization);
|
||||
entry.transitionEdges.set(tokenization, forTokenization);
|
||||
|
||||
// Also ensures that the target tokenization's data (accumulating the pairings)
|
||||
// is made available within the top-level map.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
ExtendedEditOperation,
|
||||
generateSubsetId,
|
||||
models,
|
||||
PendingTokenization,
|
||||
TransitionEdge,
|
||||
SearchQuotientSpur,
|
||||
traceInsertEdits
|
||||
} from '@keymanapp/lm-worker/test-index';
|
||||
|
|
@ -110,7 +110,7 @@ describe('ContextTokenization', function() {
|
|||
|
||||
// We _could_ flesh this out a bit more... but it's not really needed for this test.
|
||||
const edgeWindow = buildEdgeWindow(tokens, emptyTransform, false, testEdgeWindowSpec);
|
||||
let transitionEdits: PendingTokenization = {
|
||||
let transitionEdits: TransitionEdge = {
|
||||
alignment: {
|
||||
merges: [],
|
||||
splits: [],
|
||||
|
|
@ -143,7 +143,7 @@ describe('ContextTokenization', function() {
|
|||
|
||||
// We _could_ flesh this out a bit more... but it's not really needed for this test.
|
||||
const edgeWindow = buildEdgeWindow(tokens, emptyTransform, false, testEdgeWindowSpec);
|
||||
let transitionEdits: PendingTokenization = {
|
||||
let transitionEdits: TransitionEdge = {
|
||||
alignment: {
|
||||
merges: [],
|
||||
splits: [],
|
||||
|
|
|
|||
|
|
@ -627,8 +627,8 @@ describe('TokenizationSubsetBuilder', function() {
|
|||
|
||||
assert.equal(subsetBuilder.subsets.size, 1); // All transforms have similar impacts.
|
||||
const subset = [...subsetBuilder.subsets.values()][0];
|
||||
assert.equal(subset.pendingSet.size, 1); // Built from only one tokenization
|
||||
assert.deepEqual(subset.pendingSet.get(baseTokenization).inputs,
|
||||
assert.equal(subset.transitionEdges.size, 1); // Built from only one tokenization
|
||||
assert.deepEqual(subset.transitionEdges.get(baseTokenization).inputs,
|
||||
inputDistribution.map((sample) => {
|
||||
const map = new Map<number, Transform>();
|
||||
map.set(0, sample.sample);
|
||||
|
|
@ -655,18 +655,18 @@ describe('TokenizationSubsetBuilder', function() {
|
|||
|
||||
assert.equal(subsetBuilder.subsets.size, 2); // All transforms have similar impacts.
|
||||
const subsets = [...subsetBuilder.subsets.values()];
|
||||
subsets.forEach((subset) => assert.equal(subset.pendingSet.size, 1)); // Built from only one tokenization
|
||||
subsets.forEach((subset) => assert.equal(subset.transitionEdges.size, 1)); // Built from only one tokenization
|
||||
|
||||
const distributionWithoutWhitespace = inputDistribution.slice(0, inputDistribution.length-1);
|
||||
const extendingSubset = subsets.find((subset) => subset.pendingSet.get(baseTokenization).inputs.length > 1);
|
||||
assert.deepEqual(extendingSubset.pendingSet.get(baseTokenization).inputs,
|
||||
const extendingSubset = subsets.find((subset) => subset.transitionEdges.get(baseTokenization).inputs.length > 1);
|
||||
assert.deepEqual(extendingSubset.transitionEdges.get(baseTokenization).inputs,
|
||||
distributionWithoutWhitespace.map((sample) => {
|
||||
const map = new Map<number, Transform>();
|
||||
map.set(0, sample.sample);
|
||||
return { sample: map, p: sample.p };
|
||||
}));
|
||||
|
||||
const whitespaceSubset = subsets.find((subset) => subset.pendingSet.get(baseTokenization).inputs.length == 1);
|
||||
const whitespaceSubset = subsets.find((subset) => subset.transitionEdges.get(baseTokenization).inputs.length == 1);
|
||||
const whitespaceSample = inputDistribution[inputDistribution.length - 1];
|
||||
const expectedWhitespaceTransformTokenization = {
|
||||
sample: (() => {
|
||||
|
|
@ -679,7 +679,7 @@ describe('TokenizationSubsetBuilder', function() {
|
|||
})(),
|
||||
p: whitespaceSample.p
|
||||
};
|
||||
assert.deepEqual(whitespaceSubset.pendingSet.get(baseTokenization).inputs, [expectedWhitespaceTransformTokenization]);
|
||||
assert.deepEqual(whitespaceSubset.transitionEdges.get(baseTokenization).inputs, [expectedWhitespaceTransformTokenization]);
|
||||
});
|
||||
|
||||
it("builds different subsets for transforms resulting in different total lengths and token count", () => {
|
||||
|
|
@ -707,54 +707,54 @@ describe('TokenizationSubsetBuilder', function() {
|
|||
|
||||
const subsets = [...subsetBuilder.subsets.values()];
|
||||
const sameTokenLen4Subset = subsets.find((subset) => {
|
||||
const dataForSet = subset.pendingSet.get(baseTokenization);
|
||||
const dataForSet = subset.transitionEdges.get(baseTokenization);
|
||||
const totalMass = dataForSet.inputs.reduce((accum, curr) => accum + curr.p, 0);
|
||||
// Thanks, floating-point precision.
|
||||
// Should land both the 'é' (delete 1) and empty-string transform (that lacks deletes)
|
||||
return Math.abs(totalMass - .45) < 1e-8;
|
||||
});
|
||||
assert.isOk(sameTokenLen4Subset);
|
||||
assert.equal(sameTokenLen4Subset.pendingSet.get(baseTokenization).inputs.length, 2);
|
||||
assert.equal(sameTokenLen4Subset.transitionEdges.get(baseTokenization).inputs.length, 2);
|
||||
|
||||
const sameTokenLen5Subset = subsets.find((subset) => {
|
||||
const dataForSet = subset.pendingSet.get(baseTokenization);
|
||||
const dataForSet = subset.transitionEdges.get(baseTokenization);
|
||||
const totalMass = dataForSet.inputs.reduce((accum, curr) => accum + curr.p, 0);
|
||||
// Thanks, floating-point precision.
|
||||
// Should land both the 't' and 's' transforms: adds 1 char, deletes none
|
||||
return Math.abs(totalMass - .35) < 1e-8;
|
||||
});
|
||||
assert.isOk(sameTokenLen5Subset);
|
||||
assert.equal(sameTokenLen5Subset.pendingSet.get(baseTokenization).inputs.length, 2);
|
||||
assert.equal(sameTokenLen5Subset.transitionEdges.get(baseTokenization).inputs.length, 2);
|
||||
|
||||
const sameTokenLen3Subset = subsets.find((subset) => {
|
||||
const dataForSet = subset.pendingSet.get(baseTokenization);
|
||||
const dataForSet = subset.transitionEdges.get(baseTokenization);
|
||||
const totalMass = dataForSet.inputs.reduce((accum, curr) => accum + curr.p, 0);
|
||||
// Thanks, floating-point precision.
|
||||
// Should land the backspace transform.
|
||||
return Math.abs(totalMass - .1) < 1e-8;
|
||||
});
|
||||
assert.isOk(sameTokenLen3Subset);
|
||||
assert.equal(sameTokenLen3Subset.pendingSet.get(baseTokenization).inputs.length, 1);
|
||||
assert.equal(sameTokenLen3Subset.transitionEdges.get(baseTokenization).inputs.length, 1);
|
||||
|
||||
const plusOneTokenSubset = subsets.find((subset) => {
|
||||
const dataForSet = subset.pendingSet.get(baseTokenization);
|
||||
const dataForSet = subset.transitionEdges.get(baseTokenization);
|
||||
const totalMass = dataForSet.inputs.reduce((accum, curr) => accum + curr.p, 0);
|
||||
// Thanks, floating-point precision.
|
||||
// Should land the backspace transform.
|
||||
return Math.abs(totalMass - .08) < 1e-8;
|
||||
});
|
||||
assert.isOk(plusOneTokenSubset);
|
||||
assert.equal(plusOneTokenSubset.pendingSet.get(baseTokenization).inputs.length, 1);
|
||||
assert.equal(plusOneTokenSubset.transitionEdges.get(baseTokenization).inputs.length, 1);
|
||||
|
||||
const plusTwoTokensSubset = subsets.find((subset) => {
|
||||
const dataForSet = subset.pendingSet.get(baseTokenization);
|
||||
const dataForSet = subset.transitionEdges.get(baseTokenization);
|
||||
const totalMass = dataForSet.inputs.reduce((accum, curr) => accum + curr.p, 0);
|
||||
// Thanks, floating-point precision.
|
||||
// Should land the backspace transform.
|
||||
return Math.abs(totalMass - .12) < 1e-8;
|
||||
});
|
||||
assert.isOk(plusTwoTokensSubset);
|
||||
assert.equal(plusTwoTokensSubset.pendingSet.get(baseTokenization).inputs.length, 1);
|
||||
assert.equal(plusTwoTokensSubset.transitionEdges.get(baseTokenization).inputs.length, 1);
|
||||
});
|
||||
|
||||
it("places compatible results from separate tokenizations in the same subset after whitespace", () => {
|
||||
|
|
@ -810,7 +810,7 @@ describe('TokenizationSubsetBuilder', function() {
|
|||
// consider their paths separately after the transition.
|
||||
assert.equal(subsetBuilder.subsets.size, 1);
|
||||
// Has entries from two different base tokenizations.
|
||||
assert.equal([...subsetBuilder.subsets.values()][0].pendingSet.size, 2);
|
||||
assert.equal([...subsetBuilder.subsets.values()][0].transitionEdges.size, 2);
|
||||
});
|
||||
|
||||
it("places compatible results from separate tokenizations in the same subset (mid-token)", () => {
|
||||
|
|
@ -874,9 +874,9 @@ describe('TokenizationSubsetBuilder', function() {
|
|||
|
||||
// sé + an, sea + n: both result in a four-char long token starting at the same point.
|
||||
// Same total amount of .deleteLeft is supported for both variations.
|
||||
const mergedSubset = subsets.find((subset) => subset.pendingSet.size);
|
||||
const mergedSubset = subsets.find((subset) => subset.transitionEdges.size);
|
||||
assert.isOk(mergedSubset);
|
||||
assert.isTrue(mergedSubset.pendingSet.has(twoCharTokenization));
|
||||
assert.isTrue(mergedSubset.pendingSet.has(threeCharTokenization));
|
||||
assert.isTrue(mergedSubset.transitionEdges.has(twoCharTokenization));
|
||||
assert.isTrue(mergedSubset.transitionEdges.has(threeCharTokenization));
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue