Date: 2025-11-13 Purpose: Derive correct Universal automaton formulas from the working lazy implementation
From src/transducer/position.rs, the lazy Position is:
struct Position {
term_index: usize, // i - position in dictionary term
num_errors: usize, // e - errors consumed
is_special: bool, // t - transposition flag
}
When match at position 1 (lines 327-331):
Some(1) => {
next.push(Position::new(i, e + 1)); // insertion
next.push(Position::new_special(i, e + 1)); // transposition start
next.push(Position::new(i + 1, e + 1)); // substitution
next.push(Position::new(i + 2, e + 1)); // direct jump
}
Interpretation:
When match at position 0 (lines 344-349):
if cv[h] { // cv[0] - match at current position
next.push(Position::new(i + 2, e));
}
Interpretation:
Universal uses I+offset#errors where offset = i - errors (approximately).
For position I+offset#errors processing input at position k:
Wait, let me recalculate this more carefully...
From position.rs:734-736:
// For I+offset#errors at input k, the concrete word position is i = offset + k
// In bit vector s_n(w,k) (which starts at k-n), position i corresponds to index: i - (k-n) = offset + n
let match_index = (max_distance as i32 + offset) as usize;
So:
For checking word position j relative to current input k:
Usual State at position I+offset#errors, input position k:
Word position: i = k + offset
Check if cv[1] = true (word position i+1):
✓ This matches my implementation! (next_match_index = n + offset + 1)
Transposing State at position I+offset#errors, input position k:
This position was created from (i-1)#(e-1) entering transposition:
So offset DOESN'T change! But we're now at the NEXT input character (k instead of k-1).
Word position: i = k + offset
We want to check if current input matches word position i-1:
❌ My implementation checks n + offset (wrong!)
In Transposing state, I should check match_index - 1 not match_index:
// WRONG (current):
let match_index = (max_distance as i32 + offset) as usize;
if match_index < bit_vector.len() && bit_vector.is_match(match_index)
// CORRECT:
let prev_match_index = (max_distance as i32 + offset - 1) as usize;
if prev_match_index < bit_vector.len() && bit_vector.is_match(prev_match_index)
Wait, but that means checking position n+offset-1, which is checking word position i-1. Let me verify this is correct...
Actually, looking at the lazy automaton again at line 345:
if cv[h] { // h = 0, so cv[0]
And cv is "offset-adjusted" (line 263), so cv[0] corresponds to the current position i, not i-1.
Hmm, let me reconsider the semantics...
Looking at line 287 again:
next.push(Position::new_special(i, e + 1)); // transposition start
This creates position i#(e+1)_t. This is:
Then on the NEXT input character, at line 345:
if cv[h] { // Check if next input matches current word position i
next.push(Position::new(i + 2, e));
}
So the state machine is:
Transposing State I+offset#errors at input position k:
The position represents word position i where we haven't advanced yet!
✓ My implementation checking match_index = n + offset is CORRECT!
But wait, then why is it failing?
Looking at my code at line 229-233:
if let Ok(trans) = UniversalPosition::new_i_with_state(
offset + 1, // ← defect candidate
errors + 1,
max_distance,
TranspositionState::Transposing,
)
I'm creating position with offset+1, but the lazy automaton creates position with SAME i!
Let me check: if we're at I+offset#errors (word position i = k+offset), and we enter transposition:
The defect is that I'm incrementing offset when entering transposition!
It should be:
UniversalPosition::new_i_with_state(
offset, // Same offset, not offset+1!
errors + 1,
max_distance,
TranspositionState::Transposing,
)
And then in the completion, we should create:
UniversalPosition::new_i_with_state(
offset + 2, // Jump ahead by 2
errors, // Back to original error count
max_distance,
TranspositionState::Usual,
)
Let me verify this makes sense:
Actually, the offset calculation gets complex. Let me just follow the lazy pattern exactly!
Match the lazy automaton behavior exactly:
Enter transposition (from i#e):
Complete transposition (from i#(e+1)_t):
So:
new_i_with_state(offset - 1, errors + 1, ...)new_i_with_state(offset + 1, errors, ...)Let me double-check with concrete example...
Actually, I think the confusion is because Universal uses I^ε conversion. Let me just empirically test different combinations!
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |