"""CRX Refined — Cluster-then-infer CRX with over-approximation detection.""" from collections import defaultdict from .crx import CRX from .grammar import Epsilon def _cluster_by_structure(sequences): """Cluster sequences by structural features.""" groups = defaultdict(list) for seq in sequences: if not seq: continue length = len(seq) if length <= 3: length_bucket = 'short' elif length <= 8: length_bucket = 'med' else: length_bucket = 'long' key = (seq[0], seq[-1], length_bucket) groups[key].append(seq) return dict(groups) def crx_refined(sequences, min_cluster=2): """Cluster-then-infer CRX.""" S = [list(s) for s in sequences if s] if not S: return Epsilon() clusters = _cluster_by_structure(S) valid_clusters = {k: v for k, v in clusters.items() if len(v) >= min_cluster} if not valid_clusters: return CRX().infer(S) best_key = max(valid_clusters, key=lambda k: len(valid_clusters[k])) best_cluster = valid_clusters[best_key] return CRX().infer(best_cluster) def crx_with_confidence(sequences, min_cluster=2): """Cluster-then-infer CRX with confidence metrics.""" S = [list(s) for s in sequences if s] if not S: return { 'grammar': Epsilon(), 'confidence': 1.0, 'n_clusters': 0, 'largest_cluster': 0, 'n_sequences': 0, } clusters = _cluster_by_structure(S) valid_clusters = {k: v for k, v in clusters.items() if len(v) >= min_cluster} if not valid_clusters: g = CRX().infer(S) return { 'grammar': g, 'confidence': 0.5, 'n_clusters': len(clusters), 'largest_cluster': max((len(v) for v in clusters.values()), default=0), 'n_sequences': len(S), } best_key = max(valid_clusters, key=lambda k: len(valid_clusters[k])) best_cluster = valid_clusters[best_key] g = CRX().infer(best_cluster) captured = sum(1 for s in S if any(_matches_cluster(g, s) for c in valid_clusters.values() for s in c)) confidence = captured / max(len(S), 1) return { 'grammar': g, 'confidence': round(confidence, 3), 'n_clusters': len(valid_clusters), 'largest_cluster': len(best_cluster), 'n_sequences': len(S), } def _matches_cluster(grammar, seq): from .grammar import match as grammar_match try: return grammar_match(grammar, seq) except Exception: return False