Date: 2025-11-13 Status: HISTORICAL SNAPSHOT; PHASE 2 COMPLETE AND SUPERSEDED BY PHASE 2D/3 IMPLEMENTATION
Phase 2 added runtime-configurable operation support to GeneralizedAutomaton. Later Phase 2d/3 implementation connected that infrastructure to transposition, merge, split, and restricted phonetic operations; see src/transducer/generalized/automaton.rs, src/transducer/generalized/position.rs, and src/transducer/generalized/mod.rs for the current implementation surface.
Added: operations: OperationSet field to GeneralizedAutomaton
Location: src/transducer/generalized/automaton.rs:106
#[derive(Debug, Clone)]
pub struct GeneralizedAutomaton {
/// Maximum edit distance n
max_distance: u8,
/// Set of operations defining the edit distance metric
operations: OperationSet,
}
Default constructor - Uses standard operations:
pub fn new(max_distance: u8) -> Self {
Self {
max_distance,
operations: OperationSet::standard(),
}
}
Custom constructor - Accepts any operation set:
pub fn with_operations(max_distance: u8, operations: OperationSet) -> Self {
Self {
max_distance,
operations,
}
}
Examples:
// Standard Levenshtein (default)
let automaton = GeneralizedAutomaton::new(2);
// With transposition
let automaton = GeneralizedAutomaton::with_operations(
2,
OperationSet::with_transposition()
);
// Custom phonetic operations
let mut ops = OperationSet::standard();
ops.add_merge("ph", "f", 0); // Future API
let automaton = GeneralizedAutomaton::with_operations(2, ops);
Test Results: All 57 existing tests pass ✅
$ cargo test --lib generalized
running 57 tests
test result: ok. 57 passed; 0 failed; 0 ignored; 0 measured
Backward Compatibility: Fully maintained
GeneralizedAutomaton::new() works unchangedGeneralizedAutomaton
├─ max_distance: u8
├─ operations: OperationSet ← NEW
└─ Methods:
├─ new(max_distance) → Self
├─ with_operations(max_distance, operations) → Self ← NEW
└─ accepts(word, input) → bool
The operations field is currently stored but not yet used in successor generation.
GeneralizedAutomaton::accepts()
└─> GeneralizedState::transition(&operations)
└─> For each position:
└─> For each op in operations.operations():
├─ Check if op.can_apply(dict_chars, query_chars)
├─ Compute successor based on op.consume_x/y/weight
└─ Add to next state
See docs/generalized/phase2_implementation_plan.md for detailed plan.
Immediate next task: Thread OperationSet through state transition methods
Thread OperationSet (~30 min)
operations: &OperationSet parameter to GeneralizedState::transition()&self.operations from automaton methodsRefactor successor generation (~2-3 hours)
Multi-character operations (~2-3 hours)
consume_x > 1 or consume_y > 1Testing (~2 hours)
Documentation (~1 hour)
Total estimated remaining time: 8-10 hours
Rather than a big-bang refactor, we're taking a methodical approach:
All existing code continues to work:
// This still works exactly as before
let automaton = GeneralizedAutomaton::new(2);
assert!(automaton.accepts("test", "tset")); // Phase 1 tests pass
Critical Phase 1 optimizations must be preserved:
Data-driven over hardcoded:
OperationSet)Composability:
let ops = OperationSetBuilder::new()
.with_standard_ops()
.with_transposition()
.with_operation(custom_phonetic_op)
.build();
Standard operations: No regression expected
Custom operations: Depends on operation count
#[inline] on hot pathsAt each phase:
src/transducer/generalized/automaton.rs
operations field and importsAdded: OperationSet parameter to GeneralizedState::transition()
Location: src/transducer/generalized/state.rs:149-154
pub fn transition(
&self,
_operations: &crate::transducer::OperationSet, // NEW
bit_vector: &CharacteristicVector,
_input_length: usize,
) -> Option<Self>
Updated: All call sites to pass &self.operations or &automaton.operations
Locations:
src/transducer/generalized/automaton.rs:313 - accepts() methodsrc/transducer/generalized/automaton.rs:405 - test_debug_identical()src/transducer/generalized/automaton.rs:473 - test_debug_one_insertion()src/transducer/generalized/automaton.rs:577 - test_debug_deletion_middle()Test Results: All 57 tests pass ✅
Date: 2025-11-13
Goal: Replace hardcoded standard operations with dynamic OperationSet iteration
Modified files:
src/transducer/generalized/state.rs
transition() - removed _ prefix from operations parametersuccessors_standard() → successors() - added operations parametersuccessors_i_type_standard() → successors_i_type() - refactored to iterate over operationssuccessors_m_type_standard() → successors_m_type() - refactored to iterate over operationsKey Changes:
for op in operations.operations()op.is_match(), op.is_deletion(), op.is_insertion(), op.is_substitution()op.weight() as u8 instead of hardcoded +1op.consume_x() > 1 || op.consume_y() > 1 (Phase 2d)Test Results: All 57 tests pass ✅
Code Example (I-type within-window logic):
// Iterate over all operations
for op in operations.operations() {
// Skip multi-char operations for now
if op.consume_x() > 1 || op.consume_y() > 1 {
continue;
}
if op.is_match() && has_match {
// Generate match successor with offset unchanged
if let Ok(succ) = GeneralizedPosition::new_i(offset, errors, self.max_distance) {
successors.push(succ);
return successors; // Early return
}
} else if op.is_deletion() && errors < self.max_distance {
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
// Delete: offset decreases
if let Ok(succ) = GeneralizedPosition::new_i(offset - 1, new_errors, self.max_distance) {
successors.push(succ);
}
}
} else if (op.is_insertion() || op.is_substitution()) && errors < self.max_distance {
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
// Insert/substitute: offset unchanged
if let Ok(succ) = GeneralizedPosition::new_i(offset, new_errors, self.max_distance) {
successors.push(succ);
}
}
}
}
What Works Now:
What's Next (Phase 2d):
consume_x/y > 1 check to enable transposition ⟨2,2,1⟩can_apply() checks)src/transducer/operation_type.rssrc/transducer/operation_set.rssrc/transducer/universal/ (reference implementation)Phase 2 is complete when:
At the time of this snapshot, the Universal automaton implementation and context-passing API were still stabilizing. Current code has since resolved those blockers:
Transposition support
GeneralizedPosition includes ITransposing and MTransposing statesGeneralizedAutomaton::with_operations(..., OperationSet::with_transposition()) accepts adjacent swapsMulti-character context support
entry_char so two-input-character phonetic operations can validate the complete pairPhase 2 achievements remain valid
The historical analysis remains useful for design rationale, but it no longer describes the active implementation state.
This is foundational work for a flexible, composable edit distance system. The infrastructure is now connected to successor generation, and later reports should treat this file as a historical snapshot rather than the active implementation plan.
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 |