decode method

Future<List<FutoSwipeCandidate>> decode(
  1. List<FutoSwipePoint> trace,
  2. FutoSwipeLayout layout, {
  3. List<String>? previousWords,
  4. required int maxCandidates,
})

Ranks lexicon words for trace.

When previousWords is null, ranking uses the encoder+decoder weights. Otherwise it adds ContextLM scores; an empty list means start of text.

Implementation

Future<List<FutoSwipeCandidate>> decode(
  List<FutoSwipePoint> trace,
  FutoSwipeLayout layout, {
  List<String>? previousWords,
  required int maxCandidates,
}) async {
  final refined = await emissions(trace, layout);
  // Both bundle configurations use the same pruning weights, so the beam
  // does not depend on whether context is enabled.
  final weights = previousWords == null ? decoderWeights : contextWeights;
  final states = ctcPrefixSearch(
    refined.logScores,
    futoSwipeTimeSteps,
    futoSwipeClassCount,
    _root,
    beam: _beamWidth,
    gamma: weights.gammaPrune,
    beta: weights.betaPrune,
  );
  final state = previousWords == null
      ? null
      : await _contextStateFor(previousWords);
  final ranked = <FutoSwipeCandidate>[];
  for (final entry in states.entries) {
    final node = entry.key;
    if (!node.isWord) continue;
    final length = node.text.length;
    final base =
        entry.value.total / math.pow(length, weights.gamma) +
        weights.beta * length;
    final surfaces = <(String, double)>[
      for (final surface in node.surfaces)
        (
          surface.text,
          base +
              weights.lambda * surface.frequency +
              (state == null
                  ? 0
                  : weights.alpha * _scoreWord(state, surface.text)),
        ),
    ]..sort((a, b) => b.$2.compareTo(a.$2));
    ranked.add(
      FutoSwipeCandidate(
        word: surfaces.first.$1,
        searchKey: node.text,
        alternatives: surfaces.skip(1).map((s) => s.$1),
        score: surfaces.first.$2,
      ),
    );
  }
  ranked.sort((a, b) => b.score.compareTo(a.score));
  return ranked.take(maxCandidates).toList(growable: false);
}