# Phase 1: Distributional Clustering ## Goal Replace `_split_by_first_symbol()` with smarter clustering based on context similarity. This is inspired by Crucio's distributional matrix but adapted for behavioral sequences. ## Current Problem Our current `_split_by_first_symbol()` is too crude: ```python # Current: split by first symbol groups[seq[0]].append(seq) # Problem: "if return" and "return if" look different but might be equivalent # if they appear in similar contexts ``` ## Crucio's Insight Symbols that appear in the same contexts are distributionally equivalent: ``` Symbol "a" appears in contexts: [_, b], [c, _], [_, _] Symbol "b" appears in contexts: [a, _], [_, c], [_, _] → If "a" and "b" have same context distribution, they're equivalent ``` ## Our Adaptation ### Step 1: Context Extraction For each symbol in all sequences, extract context pairs: ```python # Example sequences: # ["if", "return", "if", "return"] # ["return", "if", "return"] # Contexts for "return": # - ("if", "if") at position 1 # - ("if", None) at position 3 # - (None, "if") at position 0 # Contexts for "if": # - (None, "return") at position 0 # - ("return", "return") at position 1 # - ("return", None) at position 2 ``` ### Step 2: Distribution Vector For each symbol, create a distribution vector: ```python # Symbol: "return" # Context distribution: {("if", "if"): 1, ("if", None): 1, (None, "if"): 1} # Symbol: "if" # Context distribution: {(None, "return"): 1, ("return", "return"): 1, ("return", None): 1} ``` ### Step 3: Similarity Measure Compare context distributions using cosine similarity or Jaccard: ```python def context_similarity(sym1_contexts, sym2_contexts): # Compare the sets of contexts # High similarity → symbols are distributionally equivalent ``` ### Step 4: Clustering Group symbols with high similarity: ```python # Cluster 1: ["if", "while", "for"] (conditional contexts) # Cluster 2: ["return", "yield"] (return contexts) # Cluster 3: ["class", "def"] (definition contexts) ``` ### Step 5: Split Sequences by Cluster Replace first-symbol split with cluster-based split: ```python # Instead of: # groups[seq[0]].append(seq) # Do: # cluster = symbol_to_cluster[seq[0]] # groups[cluster].append(seq) ``` ## Implementation Plan ### File: `bex/distributional.py` (new) ```python """Distributional clustering for behavioral sequences. Inspired by Crucio's distributional matrix (ICSE 2026). Groups symbols by context similarity for better sequence classification. """ def extract_contexts(sequences): """Extract context pairs for each symbol.""" # For each symbol, collect (left_context, right_context) pairs pass def build_distribution_matrix(contexts): """Build distribution matrix from contexts.""" # Rows = symbols, Columns = unique contexts # M[i,j] = count of symbol i in context j pass def cluster_symbols(distribution_matrix, threshold=0.7): """Cluster symbols by distribution similarity.""" # Use cosine similarity or Jaccard # Return dict: symbol → cluster_id pass def split_by_cluster(sequences, clusters): """Split sequences by first symbol's cluster.""" # Instead of first-symbol, use cluster membership pass ``` ### File: `bex/tag_preprocessor/analyze.py` (modify) Add `--cluster-method` flag: ```python # New flag parser.add_argument('--cluster-method', choices=['first-symbol', 'distributional'], default='first-symbol', help='Method to split mixed groups') # In _recursive_split(): if cluster_method == 'distributional': clusters = cluster_symbols(sequences) return split_by_cluster(sequences, clusters) else: return _split_by_first_symbol(sequences) ``` ## Expected Benefits 1. **Better grouping**: Symbols with same context → same group 2. **More general**: Handles cases where first symbol varies 3. **Still fast**: Distributional clustering is O(n * k) where n = symbols, k = contexts ## Test Plan 1. **Unit tests**: `tests/test_distributional.py` - Test context extraction - Test distribution matrix - Test clustering - Test sequence splitting 2. **Integration test**: Run on RAGSAK with `--cluster-method distributional` - Compare grammar count and quality vs first-symbol 3. **Evaluation metric**: `grammar_structure_score()` and `lang_size_score()` - Higher structure = better grouping - Tighter grammars = better patterns captured ## Questions to Answer 1. Does distributional clustering actually improve grammar quality? 2. What similarity threshold works best? 3. How much slower is it than first-symbol? 4. Does it help on flat bags specifically?