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
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by jahorton on 2026-02-03
*
* This file adds a SearchQuotientSpur variant modeling deletion of the corresponding
* keystroke.
*/

import { LexicalModelTypes } from "@keymanapp/common-types";

import { SearchNode } from "./distance-modeler.js";
import { PathInputProperties, SearchQuotientNode } from "./search-quotient-node.js";
import { SearchQuotientSpur } from "./search-quotient-spur.js";

import Distribution = LexicalModelTypes.Distribution;
import ProbabilityMass = LexicalModelTypes.ProbabilityMass;
import Transform = LexicalModelTypes.Transform;

export class DeletionQuotientSpur extends SearchQuotientSpur {
public readonly insertLength: number = 0;
public readonly leftDeleteLength: number = 0;

constructor(
parentNode: SearchQuotientNode,
inputs: Distribution<Readonly<Transform>>,
inputSource: PathInputProperties | ProbabilityMass<Transform>
) {
super(parentNode, inputs, inputSource, parentNode.codepointLength);
}

construct(parentNode: SearchQuotientNode, inputs: ProbabilityMass<Readonly<Transform>>[], inputSource: PathInputProperties): this {
return new DeletionQuotientSpur(parentNode, inputs, inputSource) as this;
}

protected buildEdgesForNodes(baseNodes: ReadonlyArray<SearchNode>): SearchNode[] {
return baseNodes.flatMap((n) => n.buildDeletionEdges(this.inputs, this.spaceId));
}

get edgeKey(): string {
const baseKey = super.edgeKey;
return `${baseKey}DEL`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by jahorton on 2026-02-03
*
* This file adds a SearchQuotientSpur variant modeling insertion of
* lexical-entry prefix characters - an operation with no corresponding
* keystroke.
*/

import { SearchNode } from "./distance-modeler.js";
import { SearchQuotientNode } from "./search-quotient-node.js";
import { SearchQuotientSpur } from "./search-quotient-spur.js";

export class InsertionQuotientSpur extends SearchQuotientSpur {
public readonly insertLength = 1;
public readonly leftDeleteLength = 0;

constructor(
parentNode: SearchQuotientNode
) {
super(parentNode, null, null, parentNode.codepointLength + 1);
}

construct(parentNode: SearchQuotientNode): this {
return new InsertionQuotientSpur(parentNode) as this;
}

protected buildEdgesForNodes(baseNodes: ReadonlyArray<SearchNode>): SearchNode[] {
// Note that .buildInsertionEdges will not extend any nodes reached by empty-input
// or by deletions.
return baseNodes.flatMap((n) => n.buildInsertionEdges());
}

get edgeKey(): string {
return `SR[${this.parentNode.sourceRangeKey}]L${this.codepointLength}INS`;
// How will this differentiate from other cases?
// ... sourceRangeKey + codepointLength + insert count?
return '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export abstract class SearchQuotientSpur extends SearchQuotientNode {
readonly inputs?: Distribution<Transform>;
readonly inputSource?: PathInputProperties;

private parentNode: SearchQuotientNode;
protected readonly parentNode: SearchQuotientNode;
readonly spaceId: number;

readonly inputCount: number;
Expand Down Expand Up @@ -511,9 +511,7 @@ export abstract class SearchQuotientSpur extends SearchQuotientNode {
* ancestry properly handle quotient-path variance in unit tests.
*/
get edgeKey(): string {
const inputSrc = this.inputSource;
const segment = inputSrc.segment;
return `E${inputSrc.subsetId}:${segment.start}${segment.end !== undefined ? `-${segment.end}` : ''}`;
return `E${this.inputSource?.subsetId ?? ''}:${this.splitClusteringKey}`;
}

isSameNode(space: SearchQuotientNode): boolean {
Expand All @@ -540,11 +538,7 @@ export abstract class SearchQuotientSpur extends SearchQuotientNode {

// Used to identify cluster-compatible components of SearchPaths during SearchCluster split operations.
get splitClusteringKey(): string {
const pathSrc = this.inputSource;
if(!pathSrc) {
return '';
}

return `${pathSrc.segment.start}${pathSrc.segment.end == undefined ? '' : `-${pathSrc.segment.end}`}`;
const spurSeg = this.inputSource?.segment;
return `${spurSeg?.start ?? 0}${spurSeg?.end == undefined ? '' : `-${spurSeg.end}`}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by jahorton on 2026-02-03
*
* This file adds a SearchQuotientSpur variant modeling match & substitute edit
* operations in regard to the corresponding keystroke.
*/

import { LexicalModelTypes } from "@keymanapp/common-types";
import { KMWString } from "@keymanapp/web-utils";

import { SearchNode } from "./distance-modeler.js";
import { PathInputProperties, SearchQuotientNode } from "./search-quotient-node.js";
import { SearchQuotientSpur } from "./search-quotient-spur.js";

import Distribution = LexicalModelTypes.Distribution;
import ProbabilityMass = LexicalModelTypes.ProbabilityMass;
import Transform = LexicalModelTypes.Transform;

export class SubstitutionQuotientSpur extends SearchQuotientSpur {
public insertLength: number;
public leftDeleteLength: number;

constructor(
parentNode: SearchQuotientNode,
inputs: Distribution<Readonly<Transform>>,
inputSource: PathInputProperties | ProbabilityMass<Transform>
) {
// Compute this SearchPath's codepoint length & edge length.
const inputSample = inputs?.[0].sample ?? { insert: '', deleteLeft: 0 };
const insertLength = KMWString.length(inputSample.insert);
super(parentNode, inputs, inputSource, parentNode.codepointLength + insertLength - inputSample.deleteLeft);

// Compute this SearchPath's codepoint length & edge length.
this.insertLength = insertLength;
this.leftDeleteLength = inputSample.deleteLeft;
}

construct(parentNode: SearchQuotientNode, inputs: ProbabilityMass<Readonly<Transform>>[], inputSource: PathInputProperties): this {
return new SubstitutionQuotientSpur(parentNode, inputs, inputSource) as this;
}

protected buildEdgesForNodes(baseNodes: ReadonlyArray<SearchNode>): SearchNode[] {
return baseNodes.flatMap((n) => n.buildSubstitutionEdges(this.inputs, this.spaceId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export * from './correction/context-tokenization.js';
export { ContextTracker } from './correction/context-tracker.js';
export { ContextTransition } from './correction/context-transition.js';
export * from './correction/distance-modeler.js';
export * from './correction/deletion-quotient-spur.js';
export * from './correction/insertion-quotient-spur.js';
export * from './correction/substitution-quotient-spur.js';
export * from './correction/search-quotient-cluster.js';
export * from './correction/search-quotient-spur.js';
export * from './correction/search-quotient-node.js';
Expand Down
Loading