Assertions now use isinstance() on AST nodes (Concat, Alt, Plus, Optional, Star, Symbol, Empty) instead of comparing SORE strings.
136 lines
4.3 KiB
Python
136 lines
4.3 KiB
Python
"""Tests for CRX Refined (cluster-then-infer)."""
|
|
|
|
import pytest
|
|
from bex.crx_refined import crx_refined, crx_with_confidence, _cluster_by_structure
|
|
from bex.crx import CRX
|
|
from bex.grammar import (
|
|
Symbol, Concat, Alt, Plus, Optional, Star, Epsilon, Empty,
|
|
match, alphabet,
|
|
)
|
|
|
|
|
|
class TestClusterByStructure:
|
|
def test_empty(self):
|
|
assert _cluster_by_structure([]) == {}
|
|
|
|
def test_single_sequence(self):
|
|
result = _cluster_by_structure([['a', 'b', 'c']])
|
|
assert len(result) == 1
|
|
assert ('a', 'c', 'short') in result
|
|
|
|
def test_same_start_end_same_length(self):
|
|
seqs = [['a', 'b', 'c'], ['a', 'd', 'c']]
|
|
result = _cluster_by_structure(seqs)
|
|
assert len(result) == 1
|
|
|
|
def test_different_start_end(self):
|
|
seqs = [['a', 'b', 'c'], ['x', 'y', 'z']]
|
|
result = _cluster_by_structure(seqs)
|
|
assert len(result) == 2
|
|
|
|
def test_length_buckets(self):
|
|
seqs = [
|
|
['a', 'b', 'a'],
|
|
['a', 'b', 'c', 'a'],
|
|
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'a'],
|
|
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a'],
|
|
]
|
|
result = _cluster_by_structure(seqs)
|
|
keys = set(result.keys())
|
|
assert ('a', 'a', 'short') in keys
|
|
assert ('a', 'a', 'med') in keys
|
|
assert ('a', 'a', 'long') in keys
|
|
|
|
|
|
class TestCrxRefined:
|
|
def test_empty(self):
|
|
assert isinstance(crx_refined([]), Epsilon)
|
|
|
|
def test_single_sequence(self):
|
|
result = crx_refined([['a', 'b', 'c']])
|
|
assert result is not None
|
|
assert match(result, ['a', 'b', 'c'])
|
|
|
|
def test_identical_sequences(self):
|
|
seqs = [['a', 'b', 'c']] * 5
|
|
result = crx_refined(seqs)
|
|
alpha = alphabet(result)
|
|
assert 'a' in alpha
|
|
assert 'b' in alpha
|
|
|
|
def test_linear_pattern(self):
|
|
seqs = [['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'b', 'c']]
|
|
result = crx_refined(seqs)
|
|
assert result == CRX().infer(seqs)
|
|
|
|
def test_branching_pattern(self):
|
|
seqs = [['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'b', 'e']]
|
|
result = crx_refined(seqs)
|
|
assert result is not None
|
|
alpha = alphabet(result)
|
|
assert 'a' in alpha
|
|
|
|
def test_falls_back_to_standard(self):
|
|
seqs = [['a'], ['b'], ['c'], ['d']]
|
|
result = crx_refined(seqs, min_cluster=2)
|
|
assert result == CRX().infer(seqs)
|
|
|
|
|
|
class TestCrxWithConfidence:
|
|
def test_empty(self):
|
|
result = crx_with_confidence([])
|
|
assert isinstance(result['grammar'], Epsilon)
|
|
assert result['confidence'] == 1.0
|
|
assert result['n_clusters'] == 0
|
|
|
|
def test_returns_all_fields(self):
|
|
seqs = [['a', 'b', 'c'], ['a', 'b', 'c']]
|
|
result = crx_with_confidence(seqs)
|
|
assert 'grammar' in result
|
|
assert 'confidence' in result
|
|
assert 'n_clusters' in result
|
|
assert 'largest_cluster' in result
|
|
assert 'n_sequences' in result
|
|
|
|
def test_confidence_range(self):
|
|
seqs = [['a', 'b', 'c'], ['a', 'd', 'e']]
|
|
result = crx_with_confidence(seqs)
|
|
assert 0.0 <= result['confidence'] <= 1.0
|
|
|
|
def test_confident_when_tight(self):
|
|
seqs = [['a', 'b', 'c']] * 10
|
|
result = crx_with_confidence(seqs)
|
|
assert result['confidence'] >= 0.9
|
|
|
|
def test_less_confident_when_diverse(self):
|
|
seqs = [
|
|
['a', 'b', 'c', 'z'],
|
|
['a', 'x', 'y', 'z'],
|
|
['a', 'p', 'q', 'z'],
|
|
['a', 'r', 's', 'z'],
|
|
]
|
|
result = crx_with_confidence(seqs)
|
|
assert result['n_clusters'] >= 1
|
|
|
|
|
|
class TestComparisonWithStandard:
|
|
"""Compare refined vs standard CRX on various inputs."""
|
|
|
|
def test_linear_same_result(self):
|
|
seqs = [['a', 'b', 'c']] * 5
|
|
assert crx_refined(seqs) == CRX().infer(seqs)
|
|
|
|
def test_single_symbol(self):
|
|
seqs = [['a']] * 5
|
|
result = crx_refined(seqs)
|
|
assert isinstance(result, Symbol) and result.value == 'a'
|
|
|
|
def test_two_symbols(self):
|
|
seqs = [['a', 'b']] * 5
|
|
result = crx_refined(seqs)
|
|
assert isinstance(result, Concat) and len(result.parts) == 2
|
|
|
|
def test_disjunction(self):
|
|
seqs = [['a', 'b'], ['a', 'c']]
|
|
result = crx_refined(seqs)
|
|
assert isinstance(result, Alt) or (isinstance(result, Concat) and any(isinstance(p, Alt) for p in result.parts))
|