# 13. Replace MDL scoring with Language Size measure **Date:** 2026-07-11 **Status:** Accepted ## Context Our ensemble grammar inference uses a scoring function to select the best grammar when multiple algorithms (CRX, iDRegEx, kORE) produce candidates. The scoring function determines which grammar wins — so a bad scoring function means the ensemble picks bad grammars, even when the algorithms produce good ones. ### The concrete problem On real codebases, the ensemble would pick overly generic grammars like `info+` over specific ones like `info.file.template.shell.service+`. The generic grammar accepts an astronomically large language (every permutation of `info` at every length), while the specific grammar accepts exactly one word of each length. Any human would pick the specific one. Our scorer picked the generic one. ### Why MDL failed We used Minimum Description Length (MDL): `score = model_cost + data_cost`, where `model_cost = len(expr)` and `data_cost = Σ log₂(|L(r)| at seq length)`. The problem: `model_cost('info+') = 1` (one symbol occurrence), while `model_cost('info.file.template.shell.service+') = 5`. MDL rewards short expressions. When `model_cost` is small relative to `data_cost`, short generic patterns win even though they accept far more spurious words. ### The paper that fixed it Bex, Goethals, Penninckx, Van Gucht, and Van den Bussche published *"Learning Deterministic Regular Expressions for the Inference of Schemas from XML Data"* (arXiv:1004.2372, also VLDB 2007). They proposed the iDRegEx algorithm and evaluated two scoring measures: 1. **Language Size** (Section 4.3.1): select the expression that accepts the **fewest words** up to length `n = 2m + 1`. This directly measures specificity. 2. **MDL** (Section 4.3.2): model cost + data cost, based on Adriaans & Vitányi (2006). This rewards short expressions. Their results (Section 5, line 1470): on a corpus of synthetic regular expressions with alphabet size 5, **Language Size achieved 98% success rate while MDL achieved only 21%**. They explicitly abandoned MDL: *"Therefore in the remainder of this section we only consider iDRegEx with the language size criterion."* ## Decision Replace MDL with Language Size as the default scoring function. Keep MDL as a fallback enabled via `scoring_method='mdl'` parameter. ### Implementation ```python def lang_size_score(expr, sequences): """Language Size: Σ |L(r)|_len(seq) — words at each sequence length. From Bex et al. (arXiv:1004.2372), Section 4.3.1. Lower is better — the grammar that accepts the fewest words wins. """ if not sequences: return lang_size(expr, 2 * model_cost(expr) + 1) total = 0 for seq in sequences: total += _count_words_fast(expr, len(seq)) return total ``` Counts words at exactly the lengths present in the input sequences, not all lengths 0..n. This is a practical adaptation of the paper's measure: the paper evaluates all candidates at the same fixed n, but our candidates have different n values. Counting at observed sequence lengths gives the same result — the grammar accepting the fewest words at the relevant lengths wins. The existing `_count_words_fast` function already computes the exact word count needed by this measure. No new algorithms required — just a different aggregation. ## Consequences **Positive:** - Ensemble picks specific grammars over generic ones (98% vs 21% on Bex's corpus) - `_count_words_fast` already exists and is LRU-cached — zero new code for the hard part - `model_cost` still used for tie-breaking (shortest expression when language sizes equal) **Negative:** - `lang_size` can be expensive for expressions with large alphabets (exponential in worst case), but `n = 2*model_cost + 1` keeps it bounded in practice - Old MDL results stored in YAML will have different scores than new runs — not a compatibility issue since scores are internal selection criteria, not persisted **Migration:** - `mdl_score` preserved as fallback: `scoring_method='mdl'` - `mdl_score_simple` in ensemble.py updated to use `lang_size_score` by default - CLI flag `--scoring-method` controls which is used (default: `langsize`)