Status: In Progress (Phase 2) Start Date: 2025-11-11 Estimated Completion: 2025-12-11 (30 days) Latest Update: 2025-11-11
This plan implements restricted substitutions for lazy (parameterized) Levenshtein automata using zero-cost generic traits. Key innovations:
Performance Targets:
Duration: Days 1-2 Status: ✅ Complete (2025-11-11)
COMPARISON_REPORT.md with lazy/eager terminology sectiondocs/concepts/LAZY_VS_EAGER_AUTOMATA.md comprehensive guidedocs/migration/LAZY_EAGER_TERMINOLOGY.md deprecation strategysrc/transducer/substitution_policy.rs with ZST traitsrc/transducer/ and src/transducer/universal/ pathsDuration: Days 3-5 Status: 🔄 In Progress (Day 3) Completion: 33% (1/3 tasks done)
✅ SubstitutionPolicy trait (Complete)
Unrestricted ZST implementationRestricted<'a> with set reference⏳ SubstitutionSet type (Next)
new(), allow(), allow_byte(), contains()phonetic_basic(), keyboard_qwerty(), leet_speak()⏳ Module Integration (After SubstitutionSet)
src/transducer/mod.rssrc/prelude.rsCargo.toml if neededSubstitutionSet Backend: HashSet<(u8, u8)> with FxHasher
Preset Builders:
phonetic_basic(): Common phonetic equivalences (f/ph, c/k, s/z)keyboard_qwerty(): Adjacent key pairs for typo toleranceleet_speak(): Common substitutions (3/e, @/a, 0/o)Memory Layout:
SubstitutionSet:
allowed: FxHashSet<(u8, u8)> // ~48 bytes + heap data
Restricted<'a>:
set: &'a SubstitutionSet // 8 bytes (pointer)
Unrestricted:
// Zero bytes (ZST)
src/transducer/
├── substitution_policy.rs [✅ Complete - 280 lines]
├── substitution_set.rs [⏳ Next - est. 350 lines]
└── mod.rs [⏳ Update exports]
Duration: Days 6-9 Status: ⏹️ Not Started Dependencies: Phase 2 complete
Modify characteristic_vector function
P: SubstitutionPolicy parameterdict == query || policy.is_allowed(dict, query)Thread policy through transitions
transition_positiontransition_standardtransition_transpositiontransition_merge_splittransition_state_pooledUpdate AutomatonZipper
policy: P fieldP: SubstitutionPolicyUpdate Transducer API
Transducer<D, P = Unrestricted>with_substitutions() constructorHot loop: characteristic_vector called ~100-200 times per query
Performance impact: +0.5µs per 20µs query = 2.5% worst case
#[test]
fn test_unrestricted_unchanged() {
// Verify existing tests pass with generic but unrestricted
let dict = DynamicDawg::from_terms(vec!["test"]);
let transducer = Transducer::new(dict, Algorithm::Standard);
let results: Vec<_> = transducer.query("test", 1).collect();
assert_eq!(results, vec!["test"]);
}
#[test]
fn test_restricted_phonetic() {
let dict = DynamicDawg::from_terms(vec!["phone"]);
let phonetic = SubstitutionSet::phonetic_basic();
let transducer = Transducer::with_substitutions(
dict,
Algorithm::Standard,
phonetic
);
// "fone" should match "phone" via f/ph substitution
let results: Vec<_> = transducer.query("fone", 1).collect();
assert!(results.contains(&"phone"));
}
Duration: Days 10-12 Status: ⏹️ Not Started Dependencies: Phase 3 complete
Extend UniversalAutomaton
substitutions: Option<SubstitutionSet> fieldwith_substitutions() constructorModify CharacteristicVector
new()word[i] == char || policy.is_allowed(word[i], char)Update accepts() method
Question: How to make eager automaton work with substitutions while staying parameter-free?
Solution: Store substitutions in automaton at construction time:
pub struct UniversalAutomaton<V: PositionVariant> {
max_distance: u8,
substitutions: Option<SubstitutionSet>, // NEW
// ... existing fields
}
impl<V: PositionVariant> UniversalAutomaton<V> {
pub fn with_substitutions(max_distance: u8, subs: SubstitutionSet) -> Self {
Self {
max_distance,
substitutions: Some(subs),
// ...
}
}
pub fn accepts(&self, word: &str, input: &str) -> bool {
let policy = if let Some(ref subs) = self.substitutions {
Restricted::new(subs)
} else {
Unrestricted
};
// Use policy in transitions...
}
}
Trade-off: Loses compile-time ZST optimization for eager, but maintains correctness.
Duration: Days 13-16 Status: ⏹️ Not Started Dependencies: Phase 3 & 4 complete
Core Principle: Eager automaton is oracle (reference implementation) for testing lazy.
// tests/lazy_eager_equivalence.rs
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn prop_lazy_matches_eager_oracle(
query in "[a-z]{1,10}",
dict_word in "[a-z]{1,10}",
distance in 1u8..=3,
substitutions in arb_substitution_set()
) {
// Oracle: Eager automaton (reference)
let eager = UniversalAutomaton::with_substitutions(distance, substitutions.clone());
let eager_accepts = eager.accepts(&dict_word, &query);
// Implementation under test: Lazy automaton
let dict = DynamicDawg::from_terms(vec![dict_word.clone()]);
let lazy = Transducer::with_substitutions(
dict,
Algorithm::Standard,
substitutions
);
let lazy_accepts = lazy.query(&query, distance as usize)
.any(|r| r == dict_word.as_str());
// MUST AGREE
prop_assert_eq!(eager_accepts, lazy_accepts);
}
}
fn arb_substitution_set() -> impl Strategy<Value = SubstitutionSet> {
prop::collection::vec(
(any::<char>(), any::<char>())
.prop_filter("ASCII only", |(a, b)| a.is_ascii() && b.is_ascii()),
0..10 // 0-10 allowed pairs
).prop_map(|pairs| SubstitutionSet::from_pairs(&pairs))
}
Duration: Days 17-20 Status: ⏹️ Not Started
Unit Tests
Integration Tests
Property-Based Tests
Regression Tests
Duration: Days 21-24 Status: ⏹️ Not Started
Zero-Cost Verification
bench_unrestricted_vs_baseline:
- Baseline (pre-generic): 20µs
- Unrestricted (generic): 20µs (<1% diff required)
Restricted Overhead
bench_restricted_overhead:
- Unrestricted: 20µs
- Restricted (phonetic): 20.5µs (<5% diff target)
Assembly Inspection
RUSTFLAGS="--emit=asm" cargo build --release
# Verify characteristic_vector with Unrestricted == baseline
Flamegraph Analysis
cargo flamegraph --bench substitution_benchmarks
# Verify no new hotspots
# Check characteristic_vector time unchanged
Duration: Days 25-27 Status: ⏹️ Not Started
API Documentation
User Guides
docs/features/restricted-substitutions.mdTesting Guide
docs/testing/differential-testing.mdMigration Guide
docs/migration/LAZY_EAGER_TERMINOLOGY.mdPerformance Report
docs/optimization/restricted-substitutions-2025-11-11/PERFORMANCE_REPORT.mdDuration: Days 28-30 Status: ⏹️ Not Started
Update Examples
examples/basic_query.rs - add substitution noteexamples/advanced_usage.rs - show restricted usageexamples/phonetic_matching.rsCI/CD Integration
CHANGELOG Update
Release Prep
Probability: Low Impact: High Mitigation:
Status: Monitoring (will verify in Phase 7)
Probability: Medium Impact: High Mitigation:
Status: Active monitoring (Phase 5)
Probability: Low Impact: Medium Mitigation:
Status: Prevention (benchmarks in Phase 7)
Probability: Low Impact: High Mitigation:
Status: Low concern (eager already validated)
Probability: Low Impact: Medium Mitigation:
Status: Prevention (Phase 1 complete)
Week 1 (Days 1-7):
✅ Phase 1: Terminology (Days 1-2) COMPLETE
🔄 Phase 2: Infrastructure (Days 3-5) IN PROGRESS (Day 3)
⏹️ Phase 3: Lazy Integration (Days 6-7) START SOON
Week 2 (Days 8-14):
⏹️ Phase 3: Lazy Integration (Days 8-9) CONTINUE
⏹️ Phase 4: Eager Support (Days 10-12)
⏹️ Phase 5: Cross-Validation (Days 13-14) START
Week 3 (Days 15-21):
⏹️ Phase 5: Cross-Validation (Days 15-16) COMPLETE
⏹️ Phase 6: Comprehensive Testing (Days 17-20)
⏹️ Phase 7: Performance Validation (Day 21) START
Week 4 (Days 22-28):
⏹️ Phase 7: Performance Validation (Days 22-24) COMPLETE
⏹️ Phase 8: Documentation (Days 25-27)
⏹️ Phase 9: Integration (Day 28) START
Week 5 (Days 29-30):
⏹️ Phase 9: Integration & Polish (Days 29-30) COMPLETE
Total: 30 days (6 weeks) Current: Day 3 (10% complete)
Tasks:
Decisions:
Next: Start SubstitutionSet implementation
Tasks:
Decisions:
#[inline(always)] for aggressive optimizationMetrics:
assert_eq!(size_of::<Unrestricted>(), 0)Next: Implement SubstitutionSet type
Planned Tasks:
Status: In progress...
docs/concepts/LAZY_VS_EAGER_AUTOMATA.mddocs/migration/LAZY_EAGER_TERMINOLOGY.mddocs/optimization/parameterized-vs-universal-2025-11-11/COMPARISON_REPORT.mdsrc/transducer/src/transducer/universal/src/transducer/substitution_policy.rssrc/transducer/substitution_set.rs (pending)Document Status: Living document, updated daily during implementation. Last Updated: 2025-11-11 (Day 3) Next Review: 2025-11-12 (Day 4)
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 |