Phase 3 successfully implemented the diagonal crossing infrastructure for the Universal Levenshtein Automaton. This includes the right-most position function (rm), diagonal crossing detection (f_n), and position type conversion (m_n) from Mitankin's thesis.
src/transducer/universal/diagonal.rs (373 lines)src/transducer/universal/mod.rs (added diagonal module export)src/transducer/universal/state.rs (updated transition signature and docs)right_most() (lines 53-73)Finds the position with maximum (e - i) value in a state set.
From thesis page 45:
rm(A) = position with max (e - i) in set A
Implementation:
pub fn right_most<'a, V: PositionVariant>(
positions: impl Iterator<Item = &'a UniversalPosition<V>>,
) -> Option<UniversalPosition<V>>
{
positions
.max_by_key(|pos| {
let offset = pos.offset();
let errors = pos.errors() as i32;
errors - offset
})
.cloned()
}
Key Features:
None for empty setsdiagonal_crossed() (lines 75-130)Checks if a position has crossed the edit graph diagonal.
From thesis page 43:
For I-type positions:
f_n(I + i#e, k) = (k ≤ 2n+1) ∧ (e ≤ i + 2n + 1 - k)
For M-type positions:
f_n(M + i#e, k) = e > i + n
Implementation:
pub fn diagonal_crossed<V: PositionVariant>(
pos: &UniversalPosition<V>,
k: usize,
max_distance: u8,
) -> bool {
let offset = pos.offset();
let errors = pos.errors() as i32;
let n = max_distance as i32;
let k = k as i32;
match pos {
UniversalPosition::INonFinal { .. } => {
(k <= 2 * n + 1) && (errors <= offset + 2 * n + 1 - k)
}
UniversalPosition::MFinal { .. } => {
errors > offset + n
}
}
}
Key Features:
convert_position() (lines 132-189)Converts positions between I-type (non-final) and M-type (final).
From thesis page 42:
I-type → M-type:
m_n(I + i#e, k) = M + (i + n + 1 - k)#e
M-type → I-type:
m_n(M + i#e, k) = I + (i - n - 1 + k)#e
Implementation:
pub fn convert_position<V: PositionVariant>(
pos: &UniversalPosition<V>,
k: usize,
max_distance: u8,
) -> Option<UniversalPosition<V>> {
let offset = pos.offset();
let errors = pos.errors();
let n = max_distance as i32;
let k = k as i32;
match pos {
UniversalPosition::INonFinal { .. } => {
let new_offset = offset + n + 1 - k;
UniversalPosition::new_m(new_offset, errors, max_distance).ok()
}
UniversalPosition::MFinal { .. } => {
let new_offset = offset - n - 1 + k;
UniversalPosition::new_i(new_offset, errors, max_distance).ok()
}
}
}
Key Features:
None if conversion violates invariantsUpdated signature (line 262):
pub fn transition(
&self,
bit_vector: &CharacteristicVector,
input_length: usize, // NEW PARAMETER
) -> Option<Self>
Documentation updated to describe full δ^∀,χ_n formula with diagonal crossing.
Diagonal crossing logic prepared but commented out (lines 292-321):
running 132 tests (all passed)
- 26 bit_vector tests
- 16 diagonal tests (NEW)
- 36 position tests
- 33 state tests
- 21 subsumption tests
Diagonal Module (16 tests):
Total Coverage: 132 tests across 5 modules
rm : I^χ_states ∪ M^χ_states → I^ε_s ∪ M^ε_s
rm(A) = position with maximum (e - i) value
Purpose: Find representative position for diagonal crossing check.
For I-type:
f_n(I + i#e, k) = (k ≤ 2n+1) ∧ (e ≤ i + 2n + 1 - k)
For M-type:
f_n(M + i#e, k) = e > i + n
Purpose: Determine if position has crossed edit graph diagonal.
I → M:
m_n(I + i#e, k) = M + (i + n + 1 - k)#e
M → I:
m_n(M + i#e, k) = I + (i - n - 1 + k)#e
For sets:
m_n(A, k) = {m_n(a, k) | a ∈ A}
Purpose: Convert position types when crossing diagonal.
Functions are generic over PositionVariant:
convert_position() returns Option<UniversalPosition<V>>:
None when conversion violates invariantsFull diagonal crossing logic implemented but commented out:
Reason: The diagonal crossing check needs to know the actual word length to determine when to convert I-type to M-type positions. In the current API, we only have input_length (k) but not word_length (p). This will be resolved in Phase 4 when building the full automaton.
16 tests cover all edge cases:
UniversalPosition typesNone for invalid conversionsCharacteristicVector used in examplestransition() signature updatedtransition() function extendedInput: State {I + 0#1, I + (-2)#2, I + (-1)#1}
Process:
Position e - i
I + 0#1 1 - 0 = 1
I + (-2)#2 2 - (-2) = 4 ← maximum
I + (-1)#1 1 - (-1) = 2
Result: rm({...}) = I + (-2)#2
Input: I + 0#3, k=2, n=2
Check:
f_n(I + 0#3, 2) = (2 ≤ 5) ∧ (3 ≤ 0 + 5 - 2)
= true ∧ (3 ≤ 3)
= true
Result: Diagonal crossed, should convert
Input: I + 0#0, k=3, n=2
Conversion:
m_n(I + 0#0, 3) = M + (0 + 2 + 1 - 3)#0
= M + 0#0
Result: I + 0#0 → M + 0#0
Input: M + (-1)#2, k=3, n=2
Check:
f_n(M + (-1)#2, 3) = (2 > -1 + 2)
= (2 > 1)
= true
Result: Diagonal crossed (M → I conversion needed)
Full Automaton Structure
Enable Diagonal Crossing
Query Interface
accepts(word, input) methodIntegration Tests
Phase 3 successfully implements the diagonal crossing infrastructure for the Universal Levenshtein Automaton. The implementation:
✓ Correctly implements rm (right-most position) ✓ Correctly implements f_n (diagonal crossing check) ✓ Correctly implements m_n (position type conversion) ✓ All formulas match the thesis exactly ✓ 16 comprehensive tests all passing ✓ All 132 universal module tests passing ✓ Clean integration with previous phases ✓ Diagonal crossing logic prepared for Phase 4
The foundation is now complete for Phase 4, which will build the full automaton and enable diagonal crossing in real queries.
Completion Date: 2025-11-11 Tests Passing: 132 / 132 Status: ✅ COMPLETE
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 |