grammar-inference-engine/bex/idregex.py

111 lines
3.3 KiB
Python

"""iDRegEx — Algorithm 4 (arXiv 1004.2372)."""
from .ikoa import ikoa
from .rwrsq import rwr_sq
from .grammar import (
Concat, Alt, Plus, Optional, Star, Symbol, Epsilon, Empty,
alphabet, count_words, lang_size as grammar_lang_size, model_cost as grammar_model_cost,
)
def is_deterministic(node):
"""Check if a k-ORE is deterministic (Glushkov determinism).
A k-ORE is deterministic iff for every subexpression Alt([r, s, ...]),
first(r) ∩ first(s) = ∅.
"""
if node is None or isinstance(node, (Empty, Epsilon)):
return True
return _check_det(node)
def _check_det(node):
"""Recursive determinism check on AST nodes."""
if isinstance(node, Symbol):
return True
if isinstance(node, (Epsilon, Empty)):
return True
if isinstance(node, (Plus, Optional, Star)):
return _check_det(node.child)
if isinstance(node, Alt):
first_sets = [_first_set(child) for child in node.parts]
for j, fs1 in enumerate(first_sets):
for fs2 in first_sets[j + 1:]:
if fs1 & fs2:
return False
for child in node.parts:
if not _check_det(child):
return False
return True
if isinstance(node, Concat):
for child in node.parts:
if not _check_det(child):
return False
return True
return True
def _first_set(node):
"""Compute first(r) — set of alphabet symbols that can appear at the start of a word in L(r)."""
if node is None or isinstance(node, Empty):
return set()
if isinstance(node, Epsilon):
return set()
if isinstance(node, Symbol):
return {node.value}
if isinstance(node, (Plus, Optional, Star)):
return _first_set(node.child)
if isinstance(node, Concat):
if node.parts:
return _first_set(node.parts[0])
return set()
if isinstance(node, Alt):
result = set()
for child in node.parts:
result |= _first_set(child)
return result
return set()
def idregex(sequences, kmax=4, N=5, criterion='langsize'):
"""
|———— Algorithm 4: iDRegEx ————|
Require: sample S
Ensure: k-ORE r
1: C ← ∅
2: for k = 1 to kmax do
3: for n = 1 to N do
4: G ← iKoa(S, k)
5: if rwr²(G) is deterministic then
6: add rwr²(G) to C
7: return best(C)
"""
C = set()
for k in range(1, kmax + 1):
for _ in range(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):
if is_deterministic(expr):
C.add(expr)
if not C:
return None
if criterion == 'langsize':
return min(C, key=lambda e: (grammar_lang_size(e, 2 * grammar_model_cost(e) + 1), _ast_size(e)))
return min(C, key=lambda e: _ast_size(e))
def _ast_size(node):
"""Count nodes in AST for length comparison."""
if isinstance(node, Symbol):
return 1
if isinstance(node, (Epsilon, Empty)):
return 1
if isinstance(node, (Plus, Optional, Star)):
return 1 + _ast_size(node.child)
if isinstance(node, (Concat, Alt)):
return 1 + sum(_ast_size(child) for child in node.parts)
return 1