# Phase 2: Decomposition Forest

## Goal

Break down complex/long behavioral sequences into shorter ones that still capture the pattern.
This helps when sequences are long and diverse, making CRX produce flat bags.

## Current Problem

Long sequences like:
```
["if", "return", "if", "return", "if", "return"]
```

CRX sees 6 symbols, tries to find a pattern, often produces flat bags like `(if|return)*`.

If we decompose into shorter examples:
```
["if", "return"]
["if", "return"]
["if", "return"]
```

CRX sees a clear pattern: `if.return` (repeated).

## Crucio's Approach

Crucio uses three decomposition strategies:
1. **Binary maximum subsequence deletion**: Split in half, delete max from each half
2. **Maximum subsequence deletion**: Delete largest contiguous chunk
3. **Subsequence replacement**: Replace a chunk with a shorter version

Key insight: Decomposed sequences must preserve grammar coverage (be valid under the same grammar).

## Our Adaptation

For behavioral sequences, we need simpler decomposition:
1. **Prefix extraction**: Take first N symbols
2. **Suffix extraction**: Take last N symbols
3. **Window extraction**: Take middle N symbols
4. **Pattern extraction**: Find repeated patterns and extract one instance

## Implementation Plan

### File: `bex/decompose.py` (new)

```python
"""Decomposition forest for behavioral sequences.

Inspired by Crucio's decomposition forest (ICSE 2026).
Breaks down long sequences into shorter ones that preserve patterns.
"""

def decompose_sequence(seq, max_length=5):
    """Decompose a sequence into shorter fragments.
    
    Strategies:
    1. If seq <= max_length, return as-is
    2. Extract prefixes of length 1..max_length
    3. Extract suffixes of length 1..max_length
    4. Extract windows of length max_length
    
    Args:
        seq: List of symbols
        max_length: Maximum fragment length
        
    Returns:
        List of fragments (shorter sequences)
    """
    if len(seq) <= max_length:
        return [seq]
    
    fragments = []
    
    # Prefixes
    for i in range(1, min(max_length + 1, len(seq))):
        fragments.append(seq[:i])
    
    # Suffixes
    for i in range(1, min(max_length + 1, len(seq))):
        fragments.append(seq[-i:])
    
    # Windows
    for start in range(0, len(seq) - max_length + 1):
        fragments.append(seq[start:start + max_length])
    
    return fragments


def decompose_all(sequences, max_length=5):
    """Decompose all sequences in a list.
    
    Args:
        sequences: List of lists of symbols
        max_length: Maximum fragment length
        
    Returns:
        List of fragments (shorter sequences)
    """
    all_fragments = []
    for seq in sequences:
        all_fragments.extend(decompose_sequence(seq, max_length))
    return all_fragments


def filter_by_coverage(fragments, min_coverage=0.5):
    """Keep only fragments that appear in at least min_coverage of original sequences.
    
    This ensures we keep patterns that are common, not rare.
    """
    from collections import Counter
    
    # Count how many original sequences each fragment appears in
    fragment_counts = Counter()
    for frag in fragments:
        fragment_counts[tuple(frag)] += 1
    
    # Keep fragments that appear frequently enough
    min_count = int(len(fragments) * min_coverage)
    return [list(frag) for frag, count in fragment_counts.items() 
            if count >= min_count]
```

### Integration with Pipeline

Add `--decompose` flag:
```python
parser.add_argument('--decompose', action='store_true',
                    help='Decompose long sequences before inference')
parser.add_argument('--max-seq-length', type=int, default=5,
                    help='Maximum sequence length after decomposition')
```

In `_infer_group`:
```python
if decompose:
    symbol_seqs = decompose_all(symbol_seqs, max_length=max_seq_length)
```

## Expected Benefits

1. **Shorter sequences**: CRX works better on shorter inputs
2. **Clearer patterns**: Decomposition reveals underlying structure
3. **Fewer flat bags**: Long diverse sequences become short uniform ones

## Test Plan

1. Unit tests: `tests/test_decompose.py`
2. Integration: Compare grammar count with/without decomposition
3. Metric: `grammar_structure_score()` should improve

## Questions to Answer

1. Does decomposition actually improve grammar quality?
2. What max_length works best?
3. How much slower is it?
4. Does it help on flat bags specifically?
