Inspired by Crucio's distributional matrix (ICSE 2026). Groups symbols by context similarity instead of first-symbol. New module: bex/distributional.py - extract_contexts(): get (left, right) context pairs - build_distribution_matrix(): symbol × context counts - cluster_symbols(): agglomerative clustering by similarity - split_by_cluster(): split sequences by cluster membership - distributional_split(): drop-in replacement for _split_by_first_symbol CLI: --cluster-method distributional (opt-in, first-symbol is default) Example: 'if' and 'while' both appear before 'return' → first-symbol: 3 groups (if, while, return) → distributional: 2 groups (if/while merged, return) 23 tests pass. Full suite: 257 tests pass.
4.6 KiB
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:
# 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:
# 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:
# 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:
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:
# 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:
# 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)
"""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:
# 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
- Better grouping: Symbols with same context → same group
- More general: Handles cases where first symbol varies
- Still fast: Distributional clustering is O(n * k) where n = symbols, k = contexts
Test Plan
-
Unit tests:
tests/test_distributional.py- Test context extraction
- Test distribution matrix
- Test clustering
- Test sequence splitting
-
Integration test: Run on RAGSAK with
--cluster-method distributional- Compare grammar count and quality vs first-symbol
-
Evaluation metric:
grammar_structure_score()andlang_size_score()- Higher structure = better grouping
- Tighter grammars = better patterns captured
Questions to Answer
- Does distributional clustering actually improve grammar quality?
- What similarity threshold works best?
- How much slower is it than first-symbol?
- Does it help on flat bags specifically?