Date: 2025-11-13 Status: PLANNING
After examining the Universal automaton codebase, I discovered:
Transposition variant is defined (src/transducer/universal/position.rs:110-129):
Transposition enum with Usual and TranspositionState variantsPositionVariant traitBut transposition successor generation is NOT implemented:
UniversalPosition<V>::successors() only handles Standard operationssuccessors_i_type_transposition() or successors_m_type_transposition() methodsThis means:
From Mitankin's thesis, transposition is defined as operation ⟨2, 2, 1.0⟩:
Transposition swaps two adjacent characters:
word[i..i+2] reversed equals input[j..j+2]word[i] = b, word[i+1] = a, input[j] = a, input[j+1] = b, then transposition appliesconsume_x/y > 1 skip checksProblem: Current successor generation only looks at single characters via bit vector.
Solution: Need to extract substrings from word and input:
// For operation with consume_x=2, consume_y=2
let dict_chars: &str = /* extract 2 chars from word at position i */;
let query_chars: &str = /* extract 2 chars from input at position j */;
if op.is_transposition() && dict_chars.chars().rev().eq(query_chars.chars()) {
// Transposition applies
}
Problem: CharacteristicVector encodes single-character matches. For transposition, we need to check TWO positions match (but in reverse order).
Current bit vector: β(a, w) returns 1 at position i if w[i] = a
For transposition: Need to check if w[i..i+2] reversed equals input[j..j+2]
Solution Options:
Option A: Extend CharacteristicVector to support multi-character lookups
impl CharacteristicVector {
/// Check if word[index..index+len] matches pattern (possibly reversed)
pub fn matches_at(&self, index: usize, pattern: &str, reverse: bool) -> bool;
}
Option B: Access word directly in successor generation (breaking current abstraction)
fn successors_i_type(
&self,
offset: i32,
errors: u8,
operations: &OperationSet,
bit_vector: &CharacteristicVector,
word: &str, // NEW parameter
input_char: char, // NEW parameter
) -> Vec<GeneralizedPosition>
Option C: Check transposition via operation's can_apply() method with extracted substrings
Recommendation: Option C - Use operation's can_apply() method. This is the cleanest and most aligned with the OperationSet design.
For I-type positions with transposition ⟨2,2,w⟩:
Current single-char logic:
offset stays same (I^ε conversion: (t+1)#e → I+t#e)offset - 1 (I^ε conversion: t#(e+1) → I+(t-1)#(e+1))offset stays sameMulti-char transposition:
(t+2)#(e+1) → I+(t+1)#(e+1)offset + 1 (advance by 1, not 0)errors + 1General formula:
let offset_delta = op.consume_x() as i32 - 1; // -1 for I^ε conversion
let new_offset = offset + offset_delta;
let new_errors = errors + op.weight() as u8;
This requires refactoring the API to pass word and input context:
// state.rs
pub fn transition(
&self,
operations: &OperationSet,
bit_vector: &CharacteristicVector,
word: &str, // NEW
input: &str, // NEW
input_position: usize, // NEW
) -> Option<Self>
Impact: This is a BREAKING change that affects all call sites.
Alternative: Store word/input in state or pass via a context struct.
fn can_apply_operation(
op: &OperationType,
word: &str,
word_position: usize,
input: &str,
input_position: usize,
) -> bool {
let consume_x = op.consume_x() as usize;
let consume_y = op.consume_y() as usize;
// Check bounds
if word_position + consume_x > word.len() {
return false;
}
if input_position + consume_y > input.len() {
return false;
}
// Extract substrings
let word_chars = &word[word_position..word_position + consume_x];
let input_chars = &input[input_position..input_position + consume_y];
// Check if operation can apply
op.can_apply(word_chars, input_chars)
}
Remove the consume_x/y > 1 skip checks and handle multi-char operations:
for op in operations.operations() {
// Compute actual word and input positions
let word_pos = (input_position as i32 + offset) as usize;
let input_pos = input_position;
// Check if operation can apply
if !can_apply_operation(op, word, word_pos, input, input_pos) {
continue;
}
// Compute successor offset based on consume_x
let offset_delta = op.consume_x() as i32 - 1; // I^ε conversion
let new_offset = offset + offset_delta;
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_i(new_offset, new_errors, self.max_distance) {
successors.push(succ);
}
}
}
#[test]
fn test_transposition_adjacent() {
let mut ops = OperationSet::standard();
ops.add_operation(OperationType::new(2, 2, 1.0)); // Transposition
let automaton = GeneralizedAutomaton::with_operations(2, ops);
// "test" vs "tset" (swap 'e' and 's')
assert!(automaton.accepts("test", "tset"));
}
#[test]
fn test_transposition_at_start() {
// "test" vs "etst" (swap at position 0)
assert!(automaton.accepts("test", "etst"));
}
#[test]
fn test_transposition_at_end() {
// "test" vs "tets" (swap 't' and 's')
assert!(automaton.accepts("test", "tets"));
}
#[test]
fn test_transposition_with_substitution() {
// Total distance 2: transposition + substitution
// "test" → "txst" (e→x) → "tsxt" (swap x and s)
assert!(automaton.accepts("test", "tsxt"));
}
Due to the complexity of implementing transposition and the API changes required, I propose deferring Phase 2d and documenting the current state as Phase 2c COMPLETE.
Complete Phase 2 as "Partial Multi-Char Support":
Document current status:
Benefits of deferring:
If we want to make progress on transposition without major API changes:
Similar to how Universal has Transposition::TranspositionState:
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GeneralizedPosition {
INonFinal { offset: i32, errors: u8 },
MFinal { offset: i32, errors: u8 },
ITransposition { offset: i32, errors: u8, pending_char: char }, // NEW
}
This allows tracking transposition state without needing full word/input context.
Pros:
Cons:
Option 1: Defer Phase 2d
Option 2: Implement Minimal Transposition
ITransposition position variantRecommendation: Option 1 - Defer until Universal automaton provides a reference implementation to validate against.
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 |