grammar-inference-engine/experiments/RESULTS.md
tobjend 36f9e9173d
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
docs: update experiment log, results, and handover for Round 19-20
2026-07-13 01:18:32 +02:00

243 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Experiment Results — Context Strategies + Reduce
**Date**: 2026-07-12
## Codebases
| Codebase | Files | Methods | Language |
|----------|-------|---------|----------|
| RAGSAK | 462 | 1594 | Kotlin |
| Flask | 24 | 1391 | Python |
## RAGSAK Results
| Strategy | Contexts | SOREs | Coverage | Notes |
|----------|----------|-------|----------|-------|
| Baseline (package) | 116 | 2 | 0.6% | Structural — useless |
| File path k=1 | 43 | 2 | 1.1% | Structural — no better |
| File path k=2 | 54 | 2 | 1.1% | Same |
| File path k=3 | 54 | 2 | 1.1% | Same |
| **First 1 symbol** | 134 | **20** | **4.6%** | Behavioral — 10× better |
| **First 2 symbols** | 141 | **39** | **9.8%** | Behavioral — 20× better |
| **First 3 symbols** | 117 | **47** | **12.0%** | **Winner** |
| Two-dim (p1,s1) | 151 | 24 | 6.3% | Hybrid — worse |
| Two-dim (p1,s2) | 119 | 33 | 8.7% | Hybrid — worse |
| Two-dim (p2,s1) | 146 | 24 | 6.3% | Hybrid — worse |
| Two-dim (p2,s2) | 117 | 33 | 8.7% | Hybrid — worse |
| Return type heuristic | 5 | 0 | 0.0% | Useless |
### Reduce Results (RAGSAK)
| Base | Threshold | Merges | Coverage | Notes |
|------|-----------|--------|----------|-------|
| k=1 | 0.05-0.4 | 0 | 4.6% | No merges — contexts too distinct |
| k=2 | 0.05-0.2 | 0 | 9.8% | No merges |
| k=2 | 0.3 | 1 | 9.6% | Merged `(JobStatus, every)` with `(JobStatus, now)` |
| k=2 | 0.4 | 2 | 9.5% | Merged 2 pairs |
| k=3 | 0.05-0.2 | 0 | 12.0% | No merges |
| k=3 | 0.3 | 1 | 11.7% | Merged `(JobStatus, every, getJobStatus)` with `(JobStatus, now, minusMinutes)` |
| k=3 | 0.4 | 1 | 11.7% | Same merge |
## Flask Results
| Strategy | Contexts | SOREs | Coverage | Notes |
|----------|----------|-------|----------|-------|
| Baseline (package) | 9 | 0 | 0.0% | Structural — useless |
| File path k=1 | 7 | 0 | 0.0% | Same |
| File path k=2 | 9 | 0 | 0.0% | Same |
| File path k=3 | 9 | 0 | 0.0% | Same |
| **First 1 symbol** | 53 | **2** | **1.4%** | Behavioral — only 2 SOREs |
| **First 2 symbols** | 96 | **18** | **7.3%** | Behavioral — 15× better |
| **First 3 symbols** | 85 | **21** | **10.7%** | **Winner** |
| Two-dim (p1,s1) | 68 | 5 | 2.2% | Hybrid — worse |
| Two-dim (p1,s2) | 96 | 22 | 10.1% | Close to behavioral |
| Two-dim (p2,s1) | 69 | 5 | 2.2% | Worse |
| Two-dim (p2,s2) | 93 | 19 | 9.5% | Close to behavioral |
| Return type heuristic | 3 | 0 | 0.0% | Useless |
### Reduce Results (Flask)
| Base | Threshold | Merges | Coverage | Notes |
|------|-----------|--------|----------|-------|
| k=1 | 0.05-0.4 | 0 | 1.4% | No merges |
| k=2 | 0.05-0.2 | 0 | 7.3% | No merges |
| k=2 | 0.3-0.4 | 1 | 7.3% | Merged `(def, boolean)` with `(def, is_boolean)` |
| k=3 | 0.05-0.2 | 0 | 10.7% | No merges |
| k=3 | 0.3-0.4 | 1 | 10.7% | Merged `(def, boolean, return)` with `(def, is_boolean, return)` |
## Cross-Codebase Comparison
| Metric | RAGSAK | Flask |
|--------|--------|-------|
| Best strategy | First 3 symbols | First 3 symbols |
| Best coverage | 12.0% | 10.7% |
| SOREs (best) | 47 | 21 |
| Reduce merges (ε=0.3) | 1 | 1 |
| Reduce impact on coverage | -0.3% | 0% |
## Key Findings
### 1. Behavioral grouping wins consistently
- Both codebases: First-k-symbols beats all other strategies
- Structural (file path) is useless — doesn't predict behavioral similarity
- Hybrid (2D) is worse than pure behavioral
### 2. Coverage ceiling is ~10-12%
- RAGSAK: 12.0% with 47 SOREs
- Flask: 10.7% with 21 SOREs
- Most methods have unique call patterns — they don't share prefixes
### 3. Reduce has minimal impact
- Very few merges at any threshold (0-2 per codebase)
- Support-weighted distance is conservative — requires very similar SOAs
- Merges that do happen don't improve coverage
- Reason: contexts created by first-k-symbols are already quite distinct
### 4. The few Reduce merges are meaningful
- RAGSAK: `(JobStatus, every, getJobStatus)``(JobStatus, now, minusMinutes)` — same polling pattern, different initial call
- Flask: `(def, boolean)``(def, is_boolean)` — same type-checking pattern, different method name
### 5. Flask is harder than RAGSAK
- Flask has fewer files (24 vs 462) but similar methods (1391 vs 1594)
- Flask has more unique methods per package — less repetition
- Flask SOREs are shorter/simpler — less compressible patterns
## Round 5: Cross-Package Exact Matches
**Hypothesis:** Some call sequences appear verbatim in multiple packages.
These are the real cross-package conventions.
**Method:** Group all sequences by exact tuple match, count packages per sequence.
**Result:** 38 exact cross-package sequences in RAGSAK. Most trivial:
- `('clearAllMocks',)` — 4 packages (test teardown)
- `('Builder',)` — 4 packages (builder pattern)
- `('Any',)` — 4 packages (Kotlin type)
Interesting ones:
- `('assumeTrue', 'isDockerAvailable', 'start', 'pullAndWarmup')` — 4 pkgs (Docker setup)
- `('isNullOrBlank', 'error', 'error')` — 3 pkgs (null check → error)
- `('sortedBy', 'map', 'toDescriptor')` — 3 pkgs (data pipeline)
- `('ObjectMapper', 'findAndRegisterModules')` — 2 pkgs (Jackson config)
**Verdict:** Exact matches too rare and mostly trivial. The real cross-package
patterns are structural, not textual — "null check → error" appears with
different method names in different packages.
## Summary of Failed/Dismissed Approaches
| Approach | Why it failed |
|----------|--------------|
| Per-package inference | Too sparse (1-3 seqs/package) |
| Reduce algorithm | Wrong abstraction level — merges states within one automaton, not across packages |
| Cross-package grouping by first symbol | 4.6% / 1.4% coverage — most groups skipped |
| Exact cross-package matches | 38 sequences, mostly trivial single-call |
| MDL vs Language Size scoring | Scoring isn't the bottleneck — pattern extraction is |
## What Actually Works
- **Behavioral grouping (first 3 symbols)** — 12% / 10.7% coverage, consistent across codebases
- **Calling context as prefix** — the right signal, but package-specific
- **GBNF conversion** — correct implementation, but input patterns too specific
## Next: Structural Coarsening + Cross-Package Detection
See `EXPERIMENT_LOG.md` for full reasoning and experiment design.
Core idea: collapse method names → categories using tree-sitter capture names.
Converts textual sequences into structural shapes that repeat across packages.
```
('isNullOrBlank', 'error', 'error') → (CALL, ERROR, ERROR)
('raise', 'ValueError', 'ValueError') → (CALL, ERROR, ERROR)
```
## Files Generated
- `experiments/results/ragsak_summary.json` — RAGSAK metrics
- `experiments/results/flask_summary.json` — Flask metrics
- `experiments/context_eval.py` — experiment runner (supports multiple codebases)
- `experiments/EXPERIMENT_LOG.md` — full experiment history and next steps
- `bex/reduce.py` — Algorithm 4 (TODS 2010) implementation
- `bex/gbnf.py` — SORE → GBNF converter
- `tests/test_reduce.py` — 24 tests for Reduce
- `tests/test_gbnf.py` — 15 tests for GBNF converter
---
## Round 20: AST Pipeline + Scoring Fixes (2026-07-13)
**Commit range:** `ea6cac5``8b3a454`
### Codebases
| Codebase | Files | Methods | Language |
|----------|-------|---------|----------|
| RAGSAK | 462 | 1609 | Kotlin |
| FastAPI | — | — | Python |
| Zod | — | — | TypeScript |
### Scoring: Language Size over MDL (ADR-13)
Abandoned MDL scoring — it rewards short expressions, so generic `info+` beat specific
`a.b.c.d.e+` (21% vs 98% success in Bex paper). Language Size (`lang_size_score`) chosen.
| Metric | Bag grammar | Structured grammar |
|--------|-------------|-------------------|
| `lang_size_score` | 9975 | 20 |
| `mdl_score` | 10^12 (clamped) | 10^12 (clamped) |
### Final Defaults
| Parameter | Before | After |
|-----------|--------|-------|
| `decompose` | False | **True** |
| `max_seq_length` | 5 | **4** |
| `idregex_refine` | False | **True** |
| `_COUNT_CAP` | 10^12 | **10^30** |
### Results
| Codebase | Grammars | Pure Bags | Structured | Bag % |
|----------|----------|-----------|------------|-------|
| RAGSAK (v4) | 126 | 6 | 120 | 4.8% |
| FastAPI (v3) | 143 | 26 | 117 | 18.2% |
| Zod (v3) | 23 | 5 | 18 | 21.7% |
**Quality breakdown:** ~85% of grammars across codebases remain orderless bags `(A|B|C)+`.
The ~15% that are structured represent real sequential flows:
- Web controller tests: `post→jsonPath→isEqualTo→exchange→expectStatus`
- API client patterns: `request→header→send→statusCode→jsonPath`
- Builder chains: `builder→field→value→build→validate`
### iDRegEx Findings
iDRegEx does NOT help at small scale. On 3-method groups, iDRegEx achieves only 3.8×
tighter (below the 10× gate threshold). The gate correctly rejects it.
| Group size | CRX lang_size | iDRegEx lang_size | Ratio |
|------------|--------------|-------------------|-------|
| 3 methods | 15 | 4 | 3.8× |
| 4 methods (`storage`) | — | — | 91× (outlier) |
Bags survive because:
1. CRX emits one grammar deterministically (no alternative to compare)
2. `lang_size_score` only ranks **between** algorithms, not within CRX's own output
3. iDRegEx is too slow for large groups (200s+ timeout on 2036m FastAPI tests)
### Key Insight
The grammar inference pipeline is fundamentally limited by the input: if methods in a
package don't share a sequential calling pattern, no algorithm can find one. The ~15%
structured grammars represent genuinely reusable patterns; the ~85% bags represent
packages with diverse, unrelated methods grouped only by directory proximity.
### Files
- `bex/grammar.py`: AST nodes, `_count_concat` memoization, `_COUNT_CAP = 10^30`
- `bex/crx.py`: CRX algorithm (AST-based)
- `bex/mdl.py`: `lang_size_score`, `model_cost`, `data_cost`
- `bex/idregex.py`: iDRegEx algorithm
- `bex/decompose.py`: sequence decomposition
- `bex/tag_preprocessor/analyze.py`: pipeline orchestration, all defaults
- `experiments/results/round20_ast_verify/`: full experiment data (v2/v3/v4)