def test_str_to_int(): r = parse('a = "hehe"') g = tx.Tree.from_recursive_ast(r) var_str2int = {'a': ['hehe', 'haha']} tx.sub_constants(g, var_str2int) f = g.to_recursive_ast() s = f.flatten() assert s == '( a = 0 )' x = '(loc = "s2") -> X((((env_alice = "left") && (env_bob = "bright"))))' var_str2int = { 'loc': ['s0', 's2'], 'env_alice': ['left', 'right'], 'env_bob': ['bleft', 'bright']} r = parse(x) print(repr(r)) g = tx.Tree.from_recursive_ast(r) print(g) tx.sub_constants(g, var_str2int) print(str(g)) f = g.to_recursive_ast() print(repr(f)) s = f.flatten() print(s) assert s == ('( ( loc = 1 ) -> ' '( X ( ( env_alice = 0 ) & ( env_bob = 1 ) ) ) )')
def test_str_to_int(): r = parse('a = "hehe"') g = tx.Tree.from_recursive_ast(r) var_str2int = {'a': ['hehe', 'haha']} tx.sub_constants(g, var_str2int) f = g.to_recursive_ast() s = f.flatten() assert s == '( a = 0 )' x = '(loc = "s2") -> X((((env_alice = "left") && (env_bob = "bright"))))' var_str2int = { 'loc': ['s0', 's2'], 'env_alice': ['left', 'right'], 'env_bob': ['bleft', 'bright'] } r = parse(x) print(repr(r)) g = tx.Tree.from_recursive_ast(r) print(g) tx.sub_constants(g, var_str2int) print(str(g)) f = g.to_recursive_ast() print(repr(f)) s = f.flatten() print(s) assert s == ('( ( loc = 1 ) -> ' '( X ( ( env_alice = 0 ) & ( env_bob = 1 ) ) ) )')
def parse_parse_test(formula, expected_length): # If expected_length is None, then the formula is malformed, and # thus we expect parsing to fail. if expected_length is not None: assert len(parse(formula)) == expected_length else: with pytest.raises(Exception): parse(formula)
def generate_JTLV_LTL(spec): """Return the LTLSPEC for JTLV. It takes as input a GRSpec object. N.B., assumes all variables are Boolean (i.e., atomic propositions). """ formula = spec.to_canon() parse(formula) # Raises exception if syntax error specLTL = spec.to_jtlv() logger.debug(''.join([str(x) for x in specLTL]) ) assumption = specLTL[0] guarantee = specLTL[1] assumption = re.sub(r'\b'+'True'+r'\b', 'TRUE', assumption) guarantee = re.sub(r'\b'+'True'+r'\b', 'TRUE', guarantee) assumption = re.sub(r'\b'+'False'+r'\b', 'FALSE', assumption) guarantee = re.sub(r'\b'+'False'+r'\b', 'FALSE', guarantee) assumption = assumption.replace('==', '=') guarantee = guarantee.replace('==', '=') assumption = assumption.replace('&&', '&') guarantee = guarantee.replace('&&', '&') assumption = assumption.replace('||', '|') guarantee = guarantee.replace('||', '|') # Replace any environment variable var in spec with e.var and replace any # system variable var with s.var for var in spec.env_vars.keys(): assumption = re.sub(r'\b'+var+r'\b', 'e.'+var, assumption) guarantee = re.sub(r'\b'+var+r'\b', 'e.'+var, guarantee) for var in spec.sys_vars.keys(): assumption = re.sub(r'\b'+var+r'\b', 's.'+var, assumption) guarantee = re.sub(r'\b'+var+r'\b', 's.'+var, guarantee) # Assumption ltl = 'LTLSPEC\n(\n' if assumption: ltl += assumption else: ltl += "TRUE" ltl += '\n);\n' # Guarantee ltl += '\nLTLSPEC\n(\n' if guarantee: ltl += guarantee else: ltl += "TRUE" ltl += '\n);' return ltl
def replace_dependent_vars(spec, bool2form): logger.debug('replacing dependent variables using map:\n\t' + str(bool2form)) vs = dict(spec.env_vars) vs.update(spec.sys_vars) logger.debug('variables:\n\t' + str(vs)) bool2subtree = dict() for boolvar, formula in bool2form.iteritems(): logger.debug('checking var: ' + str(boolvar)) if boolvar in vs: assert vs[boolvar] == 'boolean' logger.debug(str(boolvar) + ' is indeed Boolean') else: logger.debug('spec does not contain var: ' + str(boolvar)) tree = parser.parse(formula) bool2subtree[boolvar] = tx.Tree.from_recursive_ast(tree) for s in {'env_init', 'env_safety', 'env_prog', 'sys_init', 'sys_safety', 'sys_prog'}: part = getattr(spec, s) new = [] for clause in part: logger.debug('replacing in clause:\n\t' + clause) tree = spec.ast(clause) g = tx.Tree.from_recursive_ast(tree) tx.sub_bool_with_subtree(g, bool2subtree) f = g.to_recursive_ast().flatten() new.append(f) logger.debug('caluse tree after replacement:\n\t' + f) setattr(spec, s, new)
def parse_parse_check(formula, expected_length): # If expected_length is None, then the formula is malformed, and # thus we expect parsing to fail. if expected_length is not None: assert len(parse(formula)) == expected_length else: nt.assert_raises(Exception, parse, formula)
def check_var_name_conflict(f, varname): t = parser.parse(f) g = Tree.from_recursive_ast(t) v = {x.value for x in g.variables} if varname in v: raise ValueError('var name "{v}" already used'.format(v=varname)) return v
def replace_dependent_vars(spec, bool2form): logger.debug('replacing dependent variables using map:\n\t' + str(bool2form)) vs = dict(spec.env_vars) vs.update(spec.sys_vars) logger.debug('variables:\n\t' + str(vs)) bool2subtree = dict() for boolvar, formula in bool2form.items(): logger.debug('checking var: ' + str(boolvar)) if boolvar in vs: assert vs[boolvar] == 'boolean' logger.debug(str(boolvar) + ' is indeed Boolean') else: logger.debug('spec does not contain var: ' + str(boolvar)) tree = parser.parse(formula) bool2subtree[boolvar] = tx.Tree.from_recursive_ast(tree) for s in {'env_init', 'env_safety', 'env_prog', 'sys_init', 'sys_safety', 'sys_prog'}: part = getattr(spec, s) new = [] for clause in part: logger.debug('replacing in clause:\n\t' + clause) tree = spec.ast(clause) g = tx.Tree.from_recursive_ast(tree) tx.sub_bool_with_subtree(g, bool2subtree) f = g.to_recursive_ast().flatten() new.append(f) logger.debug('caluse tree after replacement:\n\t' + f) setattr(spec, s, new)
def lexer_token_precedence_test(): s = 'False' r = parse(s) assert isinstance(r, ast.nodes.Bool) assert r.value == 'False' s = 'a' r = parse(s) assert isinstance(r, ast.nodes.Var) assert r.value == 'a' s = 'x = "a"' r = parse(s) assert isinstance(r, ast.nodes.Binary) assert r.operator == '=' x, a = r.operands assert isinstance(x, ast.nodes.Var) assert x.value == 'x' assert isinstance(a, ast.nodes.Str) assert a.value == 'a' s = 'y = 1' r = parse(s) assert isinstance(r, ast.nodes.Binary) assert r.operator == '=' y, n = r.operands assert isinstance(y, ast.nodes.Var) assert y.value == 'y' assert isinstance(n, ast.nodes.Num) assert n.value == '1' s = '[] a' r = parse(s) assert isinstance(r, ast.nodes.Unary) assert r.operator == 'G' assert isinstance(r.operands[0], ast.nodes.Var) assert r.operands[0].value == 'a' s = 'a U b' r = parse(s) assert isinstance(r, ast.nodes.Binary) assert r.operator == 'U' assert isinstance(r.operands[0], ast.nodes.Var) assert r.operands[0].value == 'a' assert isinstance(r.operands[1], ast.nodes.Var) assert r.operands[1].value == 'b' s = '( a )' r = parse(s) assert isinstance(r, ast.nodes.Var) s = "(a ' = 1)" r = parse(s) assert isinstance(r, ast.nodes.Comparator) assert r.operator == '=' x = r.operands[0] assert isinstance(x, ast.nodes.Unary) assert x.operator == 'X' assert isinstance(x.operands[0], ast.nodes.Var) assert x.operands[0].value == 'a' y = r.operands[1] assert isinstance(y, ast.nodes.Num) assert y.value == '1'
def test_to_labeled_graph(): f = ('( ( p & q ) U ( ( q | ( ( p -> w ) & ( ! ( z -> b ) ) ) ) & ' '( G ( X g ) ) ) )') tree = parse(f) assert len(tree) == 18 nodes = {'p', 'q', 'w', 'z', 'b', 'g', 'G', 'U', 'X', '&', '|', '!', '->'} g = tx.Tree.from_recursive_ast(tree) h = tx.ast_to_labeled_graph(g, detailed=False) labels = {d['label'] for u, d in h.nodes(data=True)} assert labels == nodes
def test_to_labeled_graph(): f = ('( ( p & q ) U ( ( q | ( ( p -> w ) & ( ! ( z -> b ) ) ) ) & ' '( G ( X g ) ) ) )') tree = parse(f) assert len(tree) == 18 nodes = {'p', 'q', 'w', 'z', 'b', 'g', 'G', 'U', 'X', '&', '|', '!', '->'} g = tx.Tree.from_recursive_ast(tree) h = tx.ast_to_labeled_graph(g, detailed=False) labels = {d['label'] for u, d in h.nodes_iter(data=True)} assert labels == nodes
def full_name_operators_test(): formulas = { 'always eventually p': '( G ( F p ) )', 'ALwaYs EvenTUAlly(p)': '( G ( F p ) )', ('(p and q) UNtIl (q or ((p -> w) and ' 'not (z implies b))) and always next g'): ('( ( ( p & q ) U ( q | ( ( p -> w ) & ' '( ! ( z -> b ) ) ) ) ) & ( G ( X g ) ) )')} for f, correct in formulas.items(): tree = parse(f, full_operators=True) # g.write('hehe.png') assert tree.flatten() == correct, tree.flatten()
def full_name_operators_test(): formulas = { 'always eventually p': '( G ( F p ) )', 'ALwaYs EvenTUAlly(p)': '( G ( F p ) )', ('(p and q) UNtIl (q or ((p -> w) and ' 'not (z implies b))) and always next g'): ('( ( ( p & q ) U ( q | ( ( p -> w ) & ' '( ! ( z -> b ) ) ) ) ) & ( G ( X g ) ) )') } for f, correct in formulas.items(): tree = parse(f, full_operators=True) # g.write('hehe.png') assert tree.flatten() == correct, tree.flatten()
def infer_constants(formula, variables): """Enclose all non-variable names in quotes. @param formula: well-formed LTL formula @type formula: C{str} or L{LTL_AST} @param variables: domains of variables, or only their names. If the domains are given, then they are checked for ambiguities as for example a variable name duplicated as a possible value in the domain of a string variable (the same or another). If the names are given only, then a warning is raised, because ambiguities cannot be checked in that case, since they depend on what domains will be used. @type variables: C{dict} as accepted by L{GRSpec} or container of C{str} @return: C{formula} with all string literals not in C{variables} enclosed in double quotes @rtype: C{str} """ if isinstance(variables, dict): for var in variables: other_vars = dict(variables) other_vars.pop(var) _check_var_conflicts({var}, other_vars) else: logger.error('infer constants does not know the variable domains.') warnings.warn( 'infer_constants can give an incorrect result ' 'depending on the variable domains.\n' 'If you give the variable domain definitions as dict, ' 'then infer_constants will check for ambiguities.') tree = parser.parse(formula) old2new = dict() for u in tree: if u.type != 'var': continue if str(u) in variables: continue # Var (so NAME token) but not a variable # turn it into a string constant old2new[u] = nodes.Const(str(u)) nx.relabel_nodes(tree, old2new, copy=False) return str(tree)
def to_gr1c(self): """Dump to gr1c specification string. Cf. L{interfaces.gr1c}. """ def _to_gr1c_print_vars(vardict): output = "" for variable, domain in vardict.items(): if domain == "boolean": output += " " + variable elif isinstance(domain, tuple) and len(domain) == 2: output += " " + variable + " [" + str(domain[0]) + ", " + str(domain[1]) + "]" else: raise ValueError("Domain type unsupported by gr1c: " + str(domain)) return output tmp = finite_domain2ints(self) if tmp is not None: return tmp.to_gr1c() output = "ENV:" + _to_gr1c_print_vars(self.env_vars) + ";\n" output += "SYS:" + _to_gr1c_print_vars(self.sys_vars) + ";\n" output += "ENVINIT: " + "\n& ".join(["(" + parser.parse(s).to_gr1c() + ")" for s in self.env_init]) + ";\n" if len(self.env_safety) == 0: output += "ENVTRANS:;\n" else: output += ( "ENVTRANS: " + "\n& ".join(["[](" + parser.parse(s).to_gr1c() + ")" for s in self.env_safety]) + ";\n" ) if len(self.env_prog) == 0: output += "ENVGOAL:;\n\n" else: output += ( "ENVGOAL: " + "\n& ".join(["[]<>(" + parser.parse(s).to_gr1c() + ")" for s in self.env_prog]) + ";\n\n" ) output += "SYSINIT: " + "\n& ".join(["(" + parser.parse(s).to_gr1c() + ")" for s in self.sys_init]) + ";\n" if len(self.sys_safety) == 0: output += "SYSTRANS:;\n" else: output += ( "SYSTRANS: " + "\n& ".join(["[](" + parser.parse(s).to_gr1c() + ")" for s in self.sys_safety]) + ";\n" ) if len(self.sys_prog) == 0: output += "SYSGOAL:;\n" else: output += ( "SYSGOAL: " + "\n& ".join(["[]<>(" + parser.parse(s).to_gr1c() + ")" for s in self.sys_prog]) + ";\n" ) return output
def str_to_grspec(f): """Return `GRSpec` from LTL formula `f` as `str`. Formula `f` must be in the form: A -> G where each of A, G is a conjunction of terms: `B`, `[]C`, `[]<>B`. For more details on `B, C`, see [split_gr1]. @type f: `str` @rtype: [GRSpec] """ t = parser.parse(f) assert t.operator == '->' env, sys = t.operands d = {'assume': split_gr1(env), 'assert': split_gr1(sys)} return GRSpec(env_init=d['assume']['init'], env_safety=d['assume']['G'], env_prog=d['assume']['GF'], sys_init=d['assert']['init'], sys_safety=d['assert']['G'], sys_prog=d['assert']['GF'])
def synthesize(formula, env_vars=None, sys_vars=None): """Return Moore transducer if C{formula} is realizable. Variable C{dict}s have variable names as keys and their type as value. The types should be 'boolean'. These parameters are only used if formula is of type C{str}. Else, the variable dictionaries associated with the L{LTL} or L{GRSpec} object are used. @param formula: linear temporal logic formula @type formula: C{str}, L{LTL}, or L{GRSpec} @param env_vars: uncontrolled variables (inputs); used only if C{formula} is of type C{str} @type env_vars: C{dict} or None @param sys_vars: controlled variables (outputs); used only if C{formula} is of type C{str} @type sys_vars: C{dict} or None @return: symbolic Moore transducer (guards are conjunctions, not sets) @rtype: L{MooreMachine} """ if isinstance(formula, GRSpec): env_vars = formula.env_vars sys_vars = formula.sys_vars elif isinstance(formula, LTL): env_vars = formula.input_variables sys_vars = formula.output_variables all_vars = dict(env_vars) all_vars.update(sys_vars) if not all(v == 'boolean' for v in all_vars.itervalues()): raise TypeError( 'all variables should be Boolean:\n{v}'.format(v=all_vars)) if isinstance(formula, GRSpec): f = translate(formula, 'wring') else: T = parse(str(formula)) f = translate_ast(T, 'wring').flatten(env_vars=env_vars, sys_vars=sys_vars) # dump partition file s = '.inputs {inp}\n.outputs {out}'.format( inp=' '.join(env_vars), out=' '.join(sys_vars) ) with open(IO_PARTITION_FILE, 'w') as fid: fid.write(s) # call lily try: p = subprocess.Popen([LILY, '-f', f, '-syn', IO_PARTITION_FILE], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = p.stdout.read() except OSError as e: if e.errno == os.errno.ENOENT: raise Exception( 'lily.pl not found in path.\n' 'See the Lily docs for setting PERL5LIB and PATH.') else: raise dotf = open(DOTFILE, 'r') fail_msg = 'Formula is not realizable' if dotf.read(len(fail_msg)) == fail_msg: return None dotf.seek(0) g = nx.read_dot(dotf) dotf.close() moore = lily_strategy2moore(g, env_vars, sys_vars) return moore
def to_jtlv(self): """Return specification as list of two strings [assumption, guarantee]. Format is that of JTLV. Cf. L{interfaces.jtlv}. """ spec = ['', ''] desc_added = False for env_init in self.env_init: if (len(env_init) > 0): if (len(spec[0]) > 0): spec[0] += ' && \n' if (not desc_added): spec[0] += '-- valid initial env states\n' desc_added = True spec[0] += '\t' + parser.parse(env_init).to_jtlv() desc_added = False for env_safety in self.env_safety: if (len(env_safety) > 0): if (len(spec[0]) > 0): spec[0] += ' && \n' if (not desc_added): spec[0] += '-- safety assumption on environment\n' desc_added = True env_safety = parser.parse(env_safety).to_jtlv() spec[0] += '\t[](' +re.sub(r"next\s*\(", "next(", env_safety) +')' desc_added = False for prog in self.env_prog: if (len(prog) > 0): if (len(spec[0]) > 0): spec[0] += ' && \n' if (not desc_added): spec[0] += '-- justice assumption on environment\n' desc_added = True spec[0] += '\t[]<>(' + parser.parse(prog).to_jtlv() + ')' desc_added = False for sys_init in self.sys_init: if (len(sys_init) > 0): if (len(spec[1]) > 0): spec[1] += ' && \n' if (not desc_added): spec[1] += '-- valid initial system states\n' desc_added = True spec[1] += '\t' + parser.parse(sys_init).to_jtlv() desc_added = False for sys_safety in self.sys_safety: if (len(sys_safety) > 0): if (len(spec[1]) > 0): spec[1] += ' && \n' if (not desc_added): spec[1] += '-- safety requirement on system\n' desc_added = True sys_safety = parser.parse(sys_safety).to_jtlv() spec[1] += '\t[](' +re.sub(r"next\s*\(", "next(", sys_safety) +')' desc_added = False for prog in self.sys_prog: if (len(prog) > 0): if (len(spec[1]) > 0): spec[1] += ' && \n' if (not desc_added): spec[1] += '-- progress requirement on system\n' desc_added = True spec[1] += '\t[]<>(' + parser.parse(prog).to_jtlv() + ')' return spec
def test_has_operator(): t = parser.parse(' [](x & y) ') g = transformation.Tree.from_recursive_ast(t) assert gr1.has_operator(t, g, {'&'}) == '&' assert gr1.has_operator(t, g, {'G'}) == 'G'
def split_gr1(f): """Return `dict` of GR(1) subformulae. The formula `f` is assumed to be a conjunction of expressions of the form: `B`, `[]C` or `[]<>B` where: - `C` can contain "next" - `B` cannot contain "next" @param f: temporal logic formula @type f: `str` or AST @return: conjunctions of formulae A, B as `str`, grouped by keys: `'init', 'G', 'GF'` @rtype: `dict` of `str`: `list` of `str` """ # TODO: preprocess by applying syntactic identities: [][] = [] etc try: f + 's' t = parser.parse(f) except TypeError: t = f g = tx.Tree.from_recursive_ast(t) # collect boundary of conjunction operators Q = [g.root] b = list() # use lists to preserve as much given syntactic order while Q: u = Q.pop() # terminal ? if not g.succ.get(u): b.append(u) continue # operator if u.operator == '&': # use `u.operands` instead of `g.successors` # to preserve original order Q.extend(u.operands) else: b.append(u) d = {'init': list(), 'G': list(), 'GF': list()} for u in b: # terminal ? if not g.succ.get(u): d['init'].append(u) continue # some operator if u.operator != 'G': d['init'].append(u) continue # G (v, ) = u.operands # terminal in G ? if not g.succ.get(v): d['G'].append(v) continue # some operator in G if v.operator == 'F': (w, ) = v.operands d['GF'].append(w) else: # not a GF d['G'].append(v) # assert only admissible temporal operators ops = {'G', 'F', 'U', 'V', 'R'} operators = {'G': ops} ops = set(ops) ops.add('X') operators.update(init=ops, GF=ops) for part, f in d.items(): ops = operators[part] for u in f: op = has_operator(u, g, ops) if op is None: continue raise AssertionError(('found inadmissible operator "{op}" ' 'in "{f}" formula').format(op=op, f=u)) # conjoin (except for progress) init = ' & '.join(u.flatten() for u in reversed(d['init'])) d['init'] = [init] safe = ' & '.join(u.flatten() for u in reversed(d['G'])) d['G'] = [safe] # flatten individual progress formulae d['GF'] = [u.flatten() for u in d['GF']] return d
def split_gr1(f): """Return `dict` of GR(1) subformulae. The formula `f` is assumed to be a conjunction of expressions of the form: `B`, `[]C` or `[]<>B` where: - `C` can contain "next" - `B` cannot contain "next" @param f: temporal logic formula @type f: `str` or AST @return: conjunctions of formulae A, B as `str`, grouped by keys: `'init', 'G', 'GF'` @rtype: `dict` of `str`: `list` of `str` """ # TODO: preprocess by applying syntactic identities: [][] = [] etc try: f + 's' t = parser.parse(f) except TypeError: t = f g = tx.Tree.from_recursive_ast(t) # collect boundary of conjunction operators Q = [g.root] b = list() # use lists to preserve as much given syntactic order while Q: u = Q.pop() # terminal ? if not g.succ.get(u): b.append(u) continue # operator if u.operator == '&': # use `u.operands` instead of `g.successors` # to preserve original order Q.extend(u.operands) else: b.append(u) d = {'init': list(), 'G': list(), 'GF': list()} for u in b: # terminal ? if not g.succ.get(u): d['init'].append(u) continue # some operator if u.operator != 'G': d['init'].append(u) continue # G (v,) = u.operands # terminal in G ? if not g.succ.get(v): d['G'].append(v) continue # some operator in G if v.operator == 'F': (w,) = v.operands d['GF'].append(w) else: # not a GF d['G'].append(v) # assert only admissible temporal operators ops = {'G', 'F', 'U', 'V', 'R'} operators = {'G': ops} ops = set(ops) ops.add('X') operators.update(init=ops, GF=ops) for part, f in d.iteritems(): ops = operators[part] for u in f: op = has_operator(u, g, ops) if op is None: continue raise AssertionError(( 'found inadmissible operator "{op}" ' 'in "{f}" formula').format( op=op, f=u)) # conjoin (except for progress) init = ' & '.join(u.flatten() for u in reversed(d['init'])) d['init'] = [init] safe = ' & '.join(u.flatten() for u in reversed(d['G'])) d['G'] = [safe] # flatten individual progress formulae d['GF'] = [u.flatten() for u in d['GF']] return d