from __future__ import absolute_import, division, print_function
import pprint
from arpeggio import Optional, ZeroOrMore, OneOrMore, EOF, ParserPython, Kwd, RegExMatch
pp = pprint.PrettyPrinter(indent=4)
[docs]def convert_number(s):
try:
return float(s)
except:
return None
# each function returns a tuple
[docs]def symbol(): return RegExMatch(r'[a-zA-Z0-9.\-\_]+') # TODO -- surplus can be evaluated if there is a threshold, ignored for now
[docs]def tuple_key(): return Kwd("("), symbol, Kwd(","), symbol, Kwd(")")
[docs]def operator(): return [Kwd('>'), Kwd('<')]
[docs]def compare(): return [symbol, tuple_key], Optional(operator, symbol)
[docs]def group(): return Kwd("["), logic, Kwd("]")
[docs]def term(): return Optional(Kwd("not")), [compare, group]
[docs]def logic(): return term, ZeroOrMore([Kwd("and"), Kwd("or")], term)
[docs]def rule(): return Kwd("if"), logic, EOF
[docs]def evaluate_symbol(tree, state):
symbol = tree
value = state.get(symbol.value)
if value is None:
# symbol is a value, used by compare
value = convert_number(symbol.value)
return value
[docs]def evaluate_tuple_key(tree, state):
tuple_key = (tree[1].value, tree[3].value)
value = state.get(tuple_key)
return value
[docs]def evaluate_compare(tree, state):
if tree[0].rule_name == 'tuple_key':
first = evaluate_tuple_key(tree[0], state)
else:
first = evaluate_symbol(tree[0], state)
if len(tree) == 1:
if isinstance(first, int) or isinstance(first, float):
# if numeric state, evaluate whether it is present or not
return first > 0
else:
return first
else:
operator = tree[1]
last = evaluate_symbol(tree[2], state)
if operator.value == '<':
return first < last
else:
return first > last
[docs]def evaluate_group(tree, state):
logic = tree[1]
return evaluate_logic(logic, state)
[docs]def evaluate_term(tree, state):
invert = False
value = False
if tree[0].value == 'not':
invert = True
tree = tree[1:]
if tree[0].rule_name == 'group':
value = evaluate_group(tree[0], state)
elif tree[0].rule_name == 'compare':
value = evaluate_compare(tree[0], state)
if invert:
value = not value
return value
[docs]def evaluate_logic(tree, state):
head = evaluate_term(tree[0], state)
if len(tree) > 1:
tail = evaluate_logic(tree[2:], state)
operation = tree[1].value
if operation == 'and':
head = head and tail
elif operation == 'or':
head = head or tail
return head
[docs]def evaluate_rule(tree, state):
return evaluate_logic(tree[1], state)
# make parser based on "rule", defined in grammar above
rule_parser = ParserPython(rule)
[docs]def build_rule(expression):
# type: (str) -> Callable[Dict[str, bool], bool]
'''
Accepts a string representing a logical statement about the presence or absence of
various molecular entities relevant to regulation, and returns a function that
evaluates that logic with respect to actual values for the various symbols.
'''
tree = rule_parser.parse(expression)
def logic(state):
return evaluate_rule(tree, state)
return logic
[docs]def test_arpeggio():
# test logic with sets
test = "if not [GLCxt or LCTSxt or RUBxt] and FNR and not GlpR"
state_false = {'GLCxt': True, 'LCTSxt': False, 'RUBxt': True, 'FNR': True, 'GlpR': False}
state_true = {'GLCxt': False, 'LCTSxt': False, 'RUBxt': False, 'FNR': True, 'GlpR': False}
run_rule = build_rule(test)
assert run_rule(state_false) == False
assert run_rule(state_true) == True
# test logic with thresholds
test = "if not glc > 0.1"
state_false = {'glc': 0.2}
state_true = {'glc': 0.01}
run_rule = build_rule(test)
assert run_rule(state_false) == False
assert run_rule(state_true) == True
# test thresholds in sets with numeric states
test = "if not [FDP > 10 and F6P]" # same as "if not [FDP > 10 and F6P > 0]"
state_false = {'FDP': 20, 'F6P': 10}
state_true = {'FDP': 5, 'F6P': 0}
run_rule = build_rule(test)
assert run_rule(state_false) == False
assert run_rule(state_true) == True
# test tuple keys
test = "if not [(external, glc) > 0.1 and (external, lct) < 0.1]"
state_false = {
('external', 'glc'): 0.2,
('external', 'lct'): 0.05}
state_true = {
('external', 'glc'): 0.01,
('external', 'lct'): 0.5}
run_rule = build_rule(test)
assert run_rule(state_false) == False
assert run_rule(state_true) == True
print('test passed!')
return run_rule
if __name__ == '__main__':
test_arpeggio()