def _conj_owner(aut, owner, as_what): """Conjoin the lists in the attributes of `owner`. @param as_what: `'bdd' or 'prefix' or 'infix'` """ # get init = aut.init[owner] action = aut.action[owner] # compute if as_what == 'prefix': init = stx.conj_prefix(init) action = stx.conj_prefix(action) elif as_what == 'infix': init = stx.conj(init) action = stx.conj(action) elif as_what == 'bdd': def f(x, y): return aut.bdd.apply('and', x, y) init = stx.recurse_binary(f, init) action = stx.recurse_binary(f, action) else: raise Exception('unknown as_what="{s}"'.format(s=as_what)) # set aut.init[owner] = [init] aut.action[owner] = [action]
def _conj_owner(aut, owner, as_what): """Conjoin the lists in the attributes of `owner`. @param as_what: `'bdd' or 'prefix' or 'infix'` """ # get init = aut.init[owner] action = aut.action[owner] # compute if as_what == 'prefix': init = stx.conj_prefix(init) action = stx.conj_prefix(action) elif as_what == 'infix': init = stx.conj(init) action = stx.conj(action) elif as_what == 'bdd': def f(x, y): return aut.bdd.apply('and', x, y) init = stx.recurse_binary(f, init) action = stx.recurse_binary(f, action) else: raise Exception( 'unknown as_what="{s}"'.format(s=as_what)) # set aut.init[owner] = [init] aut.action[owner] = [action]
def translate(s, debug=False, until=False): """Translate action formula `s` with past to future LTL. Return: - history and prophecy variable symbol table - translated formula - initial condition of temportal testers - conjunction `c` of translated formula with transition relations of temporal testers. - list of recurrence goals If formula `s` is an action (in the sense of TLA), then the returned formula `c` is also an action. Note that if two subformulas are identical, then a fresh variable will be used for each one. The only exception are variables, for example "-X p". @type s: `str` @param debug: ensures repeatable ordering of new subformulas, to enable testing. @param until: add prophecy variables for "until" too @return: `(dvars, translated, init, action)` @rtype: `tuple` """ tree = parser.parse(s) testers = dict() context = 'bool' r = tree.flatten(testers=testers, context=context, until=until) if debug: ci = sorted(d['init'] for d in testers.values()) ct = sorted(d['trans'] for d in testers.values()) win = [d['win'] for d in testers.values() if d['win'] is not None] else: ci = (d['init'] for d in testers.values()) ct = (d['trans'] for d in testers.values()) win = [d['win'] for d in testers.values() if d['win'] is not None] init = conj(ci) trans = conj(ct) # collect new vars dvars = dict() for var_prev, d in testers.items(): dtype = d['type'] dom = d.get('dom') dvars[var_prev] = dict(type=dtype, dom=dom, owner='sys') return dvars, r, init, trans, win
def at_most_one_queen_per_diagonal(slash, n): """Return formula as `str`. @param slash: if `True`, then constrain anti-diagonals, else diagonals. """ c = list() if slash: a = -n b = n else: a = 0 b = 2 * n for k in range(a, b): if slash: ij = [(i, i + k) for i in range(n)] else: ij = [(i, k - i) for i in range(n)] ijs = [(i, j) for i, j in ij if 0 <= i < n and 0 <= j < n] if not ij: continue xijs = [_var_str(i, j) for i, j in ijs] s = mutex(xijs) c.append(s) return conj(c)
def test_logicizer_env(): g = TransitionSystem() g.vars['x'] = 'bool' g.owner = 'env' g.add_edge(0, 1, x=True) g.add_edge(1, 2, formula="x'") g.add_edge(2, 1) (env_init, env_act, sys_init, sys_act) = logicizer._graph_to_formulas(g, nodevar='k', ignore_initial=True, self_loops=True) s = stx.conj(env_act) e1 = "(((((k = 0)) => (((x <=> True)) /\ ((k' = 1)))) \n" e2 = "(((((k = 0)) => (((k' = 1)) /\ ((x <=> True)))) \n" assert s.startswith(e1) or s.startswith(e2), s e3 = ("/\ (((k = 1)) => ((x') /\ ((k' = 2))))) \n" "/\ (((k = 2)) => ((k' = 1)))) \/ (k' = k)") assert s.endswith(e3), s # test automaton aut = logicizer.graph_to_logic(g, nodevar='k', ignore_initial=True) assert 'x' in aut.vars, aut.vars assert 'k' in aut.vars, aut.vars xtype = aut.vars['x']['type'] ktype = aut.vars['k']['type'] assert xtype == 'bool', xtype assert ktype == 'int', ktype
def at_least_one_queen_per_row(n): """Return formula as `str`.""" c = list() for i in range(n): xijs = [_var_str(i, j) for j in range(n)] s = disj(xijs) c.append(s) return conj(c)
def _equality_of_pairs(pairs, fol): """Return `<< a1, a2, ... >> = << b1, b2, ... >>`. @param pairs: iterable of 2-tuples of identifiers """ s = stx.conj('({a} = {b})'.format(a=a, b=b) for a, b in pairs) r = fol.add_expr(s) return r
def _orthotope_nonempty(abx, fol): """Return condition that orthotope be non-empty.""" s = stx.conj( '({a} <= {b})'.format( a=d['a'], b=d['b']) for x, d in abx.items()) r = fol.add_expr(s) return r
def _orthotope_singleton(px, fol): """Return BDD that orthotope contains single point.""" s = stx.conj( '({a} = {b})'.format( a=d['a'], b=d['b']) for x, d in px.items()) r = fol.add_expr(s) return r
def x_in_implicant(prm, fol): r"""Return `x \in concretization(prm)`.""" px = prm._px s = stx.conj(''' ({a} <= {x}) /\ ({x} <= {b}) '''.format(x=x, a=d['a'], b=d['b']) for x, d in px.items()) r = fol.add_expr(s) return r
def _assignment_to_bdd(dvars, fol): """Return BDD from assignment to `dvars`. Handles only assignments of integer values. """ raise DeprecationWarning('use `_refine_assignment` instead') conj = stx.conj('{var} = {value}'.format(var=var, value=value) for var, value in dvars.items()) u = fol.add_expr(conj) return u
def _equality_of_pairs(pairs, fol): """Return `<< a1, a2, ... >> = << b1, b2, ... >>`. @param pairs: iterable of 2-tuples of identifiers """ s = stx.conj( '({a} = {b})'.format(a=a, b=b) for a, b in pairs) r = fol.add_expr(s) return r
def x_in_implicant(prm, fol): r"""Return `x \in concretization(prm)`.""" px = prm._px s = stx.conj(''' ({a} <= {x}) /\ ({x} <= {b}) '''.format( x=x, a=d['a'], b=d['b']) for x, d in px.items()) r = fol.add_expr(s) return r
def queens_formula(n): """Return a non-trivial propositional formula for the problem.""" # i = row index # j = column index present = at_least_one_queen_per_row(n) rows = at_most_one_queen_per_line(True, n) cols = at_most_one_queen_per_line(False, n) slash = at_most_one_queen_per_diagonal(True, n) backslash = at_most_one_queen_per_diagonal(False, n) s = conj([present, rows, cols, slash, backslash]) return s
def _assignment_to_bdd(dvars, fol): """Return BDD from assignment to `dvars`. Handles only assignments of integer values. """ raise DeprecationWarning('use `_refine_assignment` instead') conj = stx.conj( '{var} = {value}'.format(var=var, value=value) for var, value in dvars.items()) u = fol.add_expr(conj) return u
def mutex(v): """Return formula for at most one variable `True`. @param v: iterable of variables as `str` """ v = set(v) c = list() for x in v: rest = disj(y for y in v if y != x) s = '{x} -> !({rest})'.format(x=x, rest=rest) c.append(s) return conj(c)
def subseteq(varmap, fol): r"""Return `ab \subseteq uv`. This is the partial order defined by the subset relation. In the general formulation `\sqsubseteq`. """ s = stx.conj(''' ({u} <= {a}) /\ ({b} <= {v}) '''.format(a=a, b=b, u=u, v=v) for (a, b), (u, v) in varmap.items()) r = fol.add_expr(s) return r
def eq(varmap, fol): """Return `ab = uv`. The parameterization defines an injective mapping from parameter assignments to orthotopes. This is why equality of orthotopes is equivalent to equality of parameter values. """ s = stx.conj(''' ({a} = {u}) /\ ({b} = {v}) '''.format(a=a, b=b, u=u, v=v) for (a, b), (u, v) in varmap.items()) r = fol.add_expr(s) return r
def at_most_one_queen_per_line(row, n): """Return formula as `str`. @param row: if `True`, then constrain rows, else columns. """ c = list() for i in range(n): if row: xijs = [_var_str(i, j) for j in range(n)] else: xijs = [_var_str(j, i) for j in range(n)] s = mutex(xijs) c.append(s) return conj(c)
def _conjoin_type_hints(vrs, fol): """Return conjunction of type hints for `vrs` as BDD.""" r = list() for var in vrs: hints = fol.vars[var] if hints['type'] == 'bool': # The constraint `var \in BOOLEAN` will # anyway dissapear at the BDD layer. continue assert hints['type'] == 'int', hints a, b = hints['dom'] s = r'({a} <= {var}) /\ ({var} <= {b})' type_hints = s.format(a=a, b=b, var=var) r.append(type_hints) u = fol.add_expr(stx.conj(r)) return u
def _to_action(d, dvars): """Return `str` conjoining assignments and `"formula"` in `d`. @param d: (partial) mapping from variables in `dvars` to values in their range, defined by `dvars` @type d: `dict` @type dvars: `dict` """ c = list() if 'formula' in d: c.append(d['formula']) for k, v in d.items(): if k not in dvars: continue s = _assign(k, v, dvars) c.append(s) return stx.conj(c)
def masking_predicates(player, env_vars, aut): """Return substitution of hidden variables.""" masks = aut.masks_of[player] selector = ''' {r} = ( IF {mask} = 1 THEN {h} ELSE {var} ) ''' c = list() for var in env_vars: mask = masks[var] h = aut.xy_to_h[var] r = aut.xy_to_r[var] s = selector.format( r=r, mask=mask, h=h, var=var) c.append(s) s = stx.conj(c, op='/\\', sep='\n') # print('selector expression: \n {s}'.format(s=s)) return s
def _add_to_visited(values, visited, aut): """Return BDD `visted` updated with assignment `values`.""" c = list() for var, value in values.items(): t = aut.vars[var]['type'] if t == 'bool': assert value in (True, False), value if bool(value): c.append(var) else: c.append(' ~ ' + var) continue # integer assert t in ('int', 'saturating', 'modwrap'), t s = '{var} = {value}'.format(var=var, value=value) c.append(s) s = stx.conj(c) u = aut.add_expr(s) visited |= u return visited
def _sys_trans(g, nodevar, dvars): """Convert transition relation to safety formula.""" logger.debug('modeling sys transitions in logic') sys_trans = list() for u in g: pre = _assign(nodevar, u, dvars) # no successors ? if not g.succ.get(u): logger.debug('node: {u} is deadend !'.format(u=u)) sys_trans.append('({pre}) => False'.format(pre=pre)) continue post = list() for u, v, d in g.edges(u, data=True): t = dict(d) t[stx.prime(nodevar)] = v r = _to_action(t, dvars) post.append(r) c = '({pre}) => ({post})'.format(pre=pre, post=stx.disj(post)) sys_trans.append(c) s = stx.conj(sys_trans, sep='\n') return s
def _type_hints_to_formulas(self, vrs, action): r"""Return type constraint for `vrs` as `str`. If `action is False` then return type invariant `Inv`, else the action `Inv /\ Inv'`. """ r = list() for var in vrs: hints = self.vars[var] if hints['type'] == 'bool': continue assert hints['type'] == 'int', hints a, b = hints['dom'] s = r'({a} <= {var}) /\ ({var} <= {b})' type_inv = s.format(a=a, b=b, var=var) r.append(type_inv) if not action: continue type_inv_primed = s.format(a=a, b=b, var=stx.prime(var)) r.append(type_inv_primed) return stx.conj(r)
def _env_trans(g, nodevar, dvars, self_loops): """Convert environment transitions to safety formula. @type g: `networkx.MultiDigraph` @param nodevar: name of variable representing current node @type nodevar: `str` @type dvars: `dict` """ env_trans = list() for u in g: pre = _assign(nodevar, u, dvars) # no successors ? if not g.succ.get(u): env_trans.append('{pre} => False'.format(pre=pre)) if not self_loops: warnings.warn( 'Environment dead-end found.\n' 'If sys can force env to dead-end,\n' 'then GR(1) assumption becomes False,\n' 'and spec trivially True.') continue post = list() sys = list() for u, v, d in g.out_edges(u, data=True): # action t = dict(d) t[stx.prime(nodevar)] = v r = _to_action(t, dvars) post.append(r) # what sys vars ? t = {k: v for k, v in d.items() if k not in g.env_vars} r = _to_action(t, dvars) sys.append(r) # avoid sys winning env by blocking all edges # post.append(stx.conj_neg(sys)) env_trans.append('({pre}) => ({post})'.format( pre=pre, post=stx.disj(post))) s = stx.conj(env_trans, sep='\n') return s
def _type_hints_to_formulas(self, vrs, action): r"""Return type constraint for `vrs` as `str`. If `action is True` then return type invariant `Inv`, else the action `Inv /\ Inv'`. """ r = list() for var in vrs: hints = self.vars[var] if hints['type'] == 'bool': continue assert hints['type'] == 'int', hints a, b = hints['dom'] s = r'({a} <= {var}) /\ ({var} <= {b})' type_inv = s.format(a=a, b=b, var=var) r.append(type_inv) if not action: continue type_inv_primed = s.format( a=a, b=b, var=stx.prime(var)) r.append(type_inv_primed) return stx.conj(r)
def _env_trans(g, nodevar, dvars, self_loops): """Convert environment transitions to safety formula. @type g: `networkx.MultiDigraph` @param nodevar: name of variable representing current node @type nodevar: `str` @type dvars: `dict` """ env_trans = list() for u in g: pre = _assign(nodevar, u, dvars) # no successors ? if not g.succ.get(u): env_trans.append('{pre} => False'.format(pre=pre)) if not self_loops: warnings.warn('Environment dead-end found.\n' 'If sys can force env to dead-end,\n' 'then GR(1) assumption becomes False,\n' 'and spec trivially True.') continue post = list() sys = list() for u, v, d in g.out_edges(u, data=True): # action t = dict(d) t[stx.prime(nodevar)] = v r = _to_action(t, dvars) post.append(r) # what sys vars ? t = {k: v for k, v in d.items() if k not in g.env_vars} r = _to_action(t, dvars) sys.append(r) # avoid sys winning env by blocking all edges # post.append(stx.conj_neg(sys)) env_trans.append('({pre}) => ({post})'.format(pre=pre, post=stx.disj(post))) s = stx.conj(env_trans, sep='\n') return s
def _env_trans_from_sys_ts(g, nodevar, dvars): """Return safety assumption to prevent env from blocking sys.""" denv = {k: v for k, v in dvars.items() if k in g.env_vars} env_trans = list() for u in g: # no successor states ? if not g.succ.get(u): continue # collect possible next env actions c = set() for u, w, d in g.edges(u, data=True): t = _to_action(d, denv) if not t: continue c.add(t) # no next env actions ? if not c: continue post = stx.disj(c) pre = _assign(nodevar, u, dvars) env_trans.append('(({pre}) => ({post}))'.format(pre=pre, post=post)) s = stx.conj(env_trans, sep='\n') return s
def test_maxima(): prm = lat.Parameters() fol, _ = setup_aut(5, 5) s = 'x = 1 \/ x = 3 \/ x = 4' u = fol.add_expr(s) # x <= y s = '(x = 1 /\ y = 1) \/ (x = 1 /\ y = 3)' prm.p_leq_q = fol.add_expr(s) prm.p_vars = {'x'} prm.q_vars = {'y'} prm.p_to_q = {'x': 'y'} r = stx.conj('{pj} = {qj}'.format(pj=pj, qj=qj) for pj, qj in prm.p_to_q.items()) prm.p_eq_q = fol.add_expr(r) t0 = time.time() m = cov._maxima(u, prm, fol) t1 = time.time() dt = t1 - t0 log.info('`maxima` time (sec): {dt:1.2f}'.format(dt=dt)) # print result gen = fol.pick_iter(m, care_vars=['x']) c = list(gen) log.info(c)
def _env_trans_from_sys_ts(g, nodevar, dvars): """Return safety assumption to prevent env from blocking sys.""" denv = {k: v for k, v in dvars.items() if k in g.env_vars} env_trans = list() for u in g: # no successor states ? if not g.succ.get(u): continue # collect possible next env actions c = set() for u, w, d in g.edges(u, data=True): t = _to_action(d, denv) if not t: continue c.add(t) # no next env actions ? if not c: continue post = stx.disj(c) pre = _assign(nodevar, u, dvars) env_trans.append('(({pre}) => ({post}))'.format( pre=pre, post=post)) s = stx.conj(env_trans, sep='\n') return s
def _add_expr(c, aut): """Return BDD for conjunction of expressions in `c`.""" return aut.add_expr(stx.conj(c))
def _orthotope_singleton(px, fol): """Return BDD that orthotope contains single point.""" s = stx.conj('({a} = {b})'.format(a=d['a'], b=d['b']) for x, d in px.items()) r = fol.add_expr(s) return r
def _orthotope_nonempty(abx, fol): """Return condition that orthotope be non-empty.""" s = stx.conj('({a} <= {b})'.format(a=d['a'], b=d['b']) for x, d in abx.items()) r = fol.add_expr(s) return r