81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""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
|