grammar-inference-engine/bex/kore.py

74 lines
2.5 KiB
Python

"""kOREInference — Algorithm 4: iDRegEx (arXiv 1004.2372)."""
from .ikoa import ikoa
from .rwrsq import rwr_sq
from .idregex import is_deterministic
from .mdl import mdl_score
from .grammar import Epsilon, Empty, Symbol, Plus, Optional, Star, Concat, Alt, alphabet as ast_alphabet
def validate_k_ore(node, k, alphabet_set=None):
"""Check if a k-ORE satisfies the k-occurrence condition."""
if node is None or isinstance(node, (Empty, Epsilon)):
return True, "OK"
syms = alphabet_set or ast_alphabet(node)
counts = _count_symbol_occurrences(node)
violations = [f"{s}:{c}" for s, c in sorted(counts.items()) if c > k]
if violations:
return False, f"k={k} violations: {', '.join(violations)}"
return True, "OK"
def _count_symbol_occurrences(node):
"""Count how many times each symbol appears as a leaf in the AST."""
if isinstance(node, Symbol):
return {node.value: 1}
if isinstance(node, (Epsilon, Empty)):
return {}
if isinstance(node, (Plus, Optional, Star)):
return _count_symbol_occurrences(node.child)
if isinstance(node, (Concat, Alt)):
counts = {}
for p in node.parts:
for sym, cnt in _count_symbol_occurrences(p).items():
counts[sym] = counts.get(sym, 0) + cnt
return counts
return {}
def _count_nodes(node):
if isinstance(node, Symbol):
return 1
if isinstance(node, (Epsilon, Empty)):
return 1
if isinstance(node, (Plus, Optional, Star)):
return 1 + _count_nodes(node.child)
if isinstance(node, (Concat, Alt)):
return 1 + sum(_count_nodes(p) for p in node.parts)
return 1
class kOREInference:
def __init__(self, k_max=5, N=5):
self.k_max = k_max
self.N = N
def infer(self, sequences):
sequences = [s for s in sequences if s]
if not sequences:
return None
candidates = []
for k in range(1, self.k_max + 1):
for _ in range(self.N):
G = ikoa(sequences, k, num_trials=1)
if G is None:
continue
expr = rwr_sq(G)
if expr is not None and not isinstance(expr, (Empty, Epsilon)):
if is_deterministic(expr):
valid, _ = validate_k_ore(expr, k)
if valid:
candidates.append((G, expr, k))
if not candidates:
return None
return min(candidates, key=lambda c: mdl_score(c[1], sequences))