Date: 2025-10-30 Current Status: Bugs identified, partial fixes implemented, dictionary construction fix required
Cross-validation testing successfully discovered 2 critical bugs by comparing Levenshtein automaton results against SIMD-optimized distance function results. The SIMD distance functions are 100% correct - all bugs are in the automaton implementation.
Test Results: 5/16 passing (11 failures)
Bugs Found:
Status: Root cause identified, partial fix implemented, dictionary construction fix required
Problem: Dictionaries with empty strings never return empty string matches
Root Cause: Dictionary builders (DoubleArrayTrie, DAWG, etc.) do not mark root node as final when empty strings are inserted.
Test Evidence:
let dict = DoubleArrayTrie::from_terms(vec!["".to_string()]);
assert!(dict.root().is_final()); // ❌ FAILS - returns false, should be true
Fixes Implemented (Partial):
LazyTransducerCollection.java - Added root finality check (commit: 2765222)query.rs + ordered_query.rs - Added root finality checks (commit: d03c95d)Why Partial Fix Doesn't Work:
The query logic now checks if root_intersection.is_final(), but is_final() returns false because the dictionary builder didn't mark the root as final during construction.
Files Needing Fixes:
src/dictionary/double_array_trie.rs - DoubleArrayTrieBuilder::insert("")
src/dictionary/dawg.rs - DawgBuilder insert empty string
src/dictionary/dawg_optimized.rs - OptimizedDawgBuilder insert empty string
src/dictionary/dynamic_dawg.rs - DynamicDawg::insert("")
src/dictionary/pathmap.rs - PathMapDictionary insert empty string
How to Fix: Each dictionary builder's insert/construction logic must:
is_final flag as trueExample pseudocode:
fn insert(&mut self, term: &str) {
if term.is_empty() {
self.root_is_final = true; // Mark root as final
self.term_count += 1;
return;
}
// ... normal insertion logic ...
}
Test Failures Caused: 10 out of 11 failures
Impact: High - affects all algorithms (Standard, Transposition, MergeAndSplit)
Status: Identified, not yet fixed
Problem: Transposition algorithm misses valid transposition matches
Test Case:
let dict = vec!["ab", "ba", "abc"];
let transducer = Transducer::new(dat, Algorithm::Transposition);
let results = transducer.query("ab", 1).collect();
// Expected: ["ab", "ba", "abc"]
// Actual: ["ab", "abc"] // Missing "ba"
Distance Function Verification (Correct):
transposition_distance("ab", "ba") == 1 // ✓ One transposition
Root Cause: Unknown - likely one of:
Files to Investigate:
src/transducer/transition/parametric.rs - Transposition transition generation
src/transducer/state.rs - State subsumption logic
src/transducer/algorithm.rs - Algorithm::Transposition semantics
Test Failures Caused: 1 out of 11 failures
Impact: High - affects all users of Transposition algorithm
File: tests/proptest_automaton_distance_cross_validation.rs (481 lines)
Test Coverage:
Current Results:
running 16 tests
test prop_standard_automaton_distance_matches_function ... ok ✓
test prop_transposition_automaton_distance_matches_function ... ok ✓
test prop_empty_dictionary_all_algorithms ... ok ✓
test regression_tests::test_deletion_bug_cross_validation ... ok ✓
test regression_tests::test_merge_split_specific_case ... ok ✓
test prop_empty_query_all_algorithms ... FAILED ✗ (empty string bug)
test prop_duplicate_words_all_algorithms ... FAILED ✗ (empty string bug)
test prop_exact_match_only_all_algorithms ... FAILED ✗ (empty string bug)
test prop_standard_automaton_matches_linear_scan ... FAILED ✗ (empty string bug)
test prop_standard_large_dict_matches ... FAILED ✗ (empty string bug)
test prop_standard_unicode_matches ... FAILED ✗ (empty string bug)
test prop_transposition_automaton_matches_linear_scan ... FAILED ✗ (both bugs)
test prop_transposition_handles_swaps_correctly ... FAILED ✗ (both bugs)
test prop_merge_split_automaton_matches_linear_scan ... FAILED ✗ (empty string bug)
test prop_merge_split_automaton_distance_matches_function ... FAILED ✗ (empty string bug)
test regression_tests::test_transposition_specific_case ... FAILED ✗ (transposition bug)
Result: 5 passed, 11 failed
Expected After Fixes: 16/16 passing ✓
File: benches/automaton_vs_linear_scan.rs (522 lines)
Status: Ready to run, pending bug fixes
Purpose: Demonstrate that automaton is faster than linear scan with distance functions
Benchmark Groups:
Expected Results: Automaton should be 10-100x faster than linear scan for large dictionaries
Note: Benchmark can be run now, but results may be skewed by empty string handling edge cases
Status: 100% CORRECT - All distance functions pass validation
The cross-validation testing confirms:
standard_distance() - Correct for all test casestransposition_distance() - Correct for all test casesmerge_and_split_distance() - Correct for all test casesAll SIMD optimizations (Phase 4 work) are functioning correctly. The bugs are entirely in the automaton implementation, not the distance computations.
File: liblevenshtein-java/src/main/java/com/github/liblevenshtein/transducer/LazyTransducerCollection.java
Status: ✅ Empty string bug fixed (commit: 2765222)
Fix Applied:
// Check if root node is final (handles empty string case)
if (attributes.isFinal().at(attributes.dictionaryRoot())) {
final int distance =
attributes.minDistance().at(attributes.initialState(), term.length());
if (distance <= maxDistance) {
this.next = attributes.candidateFactory().build("", distance);
}
}
Testing: Not validated (Gradle version incompatibility with Java 25)
Note: Java likely has the same dictionary construction bug, but fix was applied at query level and may work if Java dictionaries handle empty strings correctly.
CROSS_VALIDATION_BUG_REPORT.md (400+ lines)
JAVA_RUST_COMPARISON_ANALYSIS.md (410 lines)
CROSS_VALIDATION_TEST_COVERAGE.md (337 lines)
CROSS_VALIDATION_STATUS.md (this document)
Total Documentation: 1,500+ lines
8304cc1 - test: Add cross-validation tests for automaton vs distance functions
0840277 - docs: Add Java vs Rust implementation comparison analysis
e3eb959 - docs: Add comprehensive cross-validation test coverage analysis
d03c95d - fix(partial): Add root finality checks to query iterators (blocked by dict bug)
fix: Add explicit root finality check for empty string support
Task: Modify all dictionary builders to mark root as final for empty strings
Files to modify:
src/dictionary/double_array_trie.rs - DoubleArrayTrieBuildersrc/dictionary/dawg.rs - DawgBuildersrc/dictionary/dawg_optimized.rs - OptimizedDawgBuildersrc/dictionary/dynamic_dawg.rs - DynamicDawgsrc/dictionary/pathmap.rs - PathMapDictionaryImplementation: Each builder needs:
// Detect empty string insertion
if term.is_empty() {
// Mark root as final
self.root_is_final = true;
// Increment count
self.term_count += 1;
return;
}
Testing:
cargo test --test proptest_automaton_distance_cross_validation prop_empty
# Should fix 10 out of 11 failing tests
Estimated Time: 2-3 hours (all 5 dictionary backends)
Task: Investigate and fix transposition transition generation
Approach:
parametric.rs transposition transition functionFiles to investigate:
src/transducer/transition/parametric.rssrc/transducer/state.rsTesting:
cargo test --test proptest_automaton_distance_cross_validation regression_tests::test_transposition_specific_case
# Should pass after fix
Estimated Time: 3-5 hours (investigation + fix)
Task: Run full cross-validation test suite
Command:
RUSTFLAGS="-C target-cpu=native" cargo test --test proptest_automaton_distance_cross_validation
Expected: 16/16 passing ✓
If all pass:
Task: Run automaton vs linear scan benchmarks
Command:
RUSTFLAGS="-C target-cpu=native" cargo bench --bench automaton_vs_linear_scan
Purpose: Demonstrate automaton performance advantage
Expected Results:
Document: Add performance results to README.md
Tasks:
Total Estimated Time: 7-10 hours of focused development
Cross-validation testing was highly effective at discovering bugs:
The test suite serves as excellent regression tests and should be run before every release to ensure automaton correctness.
Recommendation: Fix both bugs, validate with cross-validation tests, then release v0.4.1 with confidence that both automaton and distance functions are correct.
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 |