def prime(u, fol): """Prime state predicate `u`.""" support = fol.support(u) # all identifiers are unprimed ? assert not any(stx.isprimed(name) for name in support), support # avoid priming constants # (no primed identifiers are declared for constants) vrs = {name for name in support if is_variable(name, fol)} let = {var: stx.prime(var) for var in vrs} return fol.let(let, u)
def is_action_of_player(action, player, aut): """Return `True` if `action` constrains only `player`. The `player` is represented by the variables in `aut.varlist[player]`. """ support = aut.support(action) primed = {var for var in support if stx.isprimed(var)} vrs = aut.vars_of_players([player]) vrs_p = aut.prime_vars(vrs) r = primed.issubset(vrs_p) return r
def implies_type_hints(self, u, vrs): """Return `True` if `u => TypeInv` for all vars. All declared variables and constants are taken into account. """ # not named `assert_type_invariant` because the # assertion is about the state predicate `u`, # so a static conclusion. vrs = {var for var in self.vars if not stx.isprimed(var)} type_hints = _conjoin_type_hints(vrs, self) r = type_hints | ~u return r == self.true
def implies_type_hints(self, u, vrs): """Return `True` if `u => TypeInv` for all vars. All declared variables and constants are taken into account. """ # not named `assert_type_invariant` because the # assertion is about the state predicate `u`, # so a static conclusion. vrs = {var for var in self.vars if not stx.isprimed(var)} type_hints = tyh._conjoin_type_hints(vrs, self) r = type_hints | ~ u return r == self.true
def implies_type_hints(self, u, vrs=None): """Return `True` if `u => TypeHints` for `vrs`. If `vrs is None`, then all declared variables and constants are taken into account. """ # This function is not named `assert_type_invariant` # because the assertion is about the state predicate `u`, # so a static conclusion. # if vrs is None: vrs = {var for var in self.vars if not stx.isprimed(var)} type_hints = tyh._conjoin_type_hints(vrs, self) r = type_hints | ~u return r == self.true
def split_support(u, fol): """Return unprimed, primed identifiers `u` depends on. This function exists as an optimization over calling separately `unprimed_support` and `primed_support`. This function calls `fol.support` once, as opposed to twice when performing two separate calls. If optimization is irrelevant, then call those other functions, because readability counts [PEP 20]. """ support = fol.support(u) primed = {k for k in support if stx.isprimed(k)} unprimed = support - primed return unprimed, primed
def add_primed_too(table): """Return table of primed and unprimed vars. Assert `table` contains only unprimed vars. Return new table with a primed variable for each unprimed variable in `table`, in addition to the unprimed variables. """ t = dict() for var, d in table.items(): assert not stx.isprimed(var) pvar = stx.prime(var) t[var] = dict(d) t[pvar] = dict(d) return t
def vars_in_support(u, fol): """Return variables that `u` depends on. Returns unprimed identifiers for all variables that occur (primed or not) in the support of `u`. """ vrs = set() for k in fol.support(u): if stx.isprimed(k): vrs.add(stx.unprime(k)) elif is_variable(k, fol): vrs.add(k) assert vrs == (flexible_support(u, fol) | {stx.unprime(s) for s in primed_support(u, fol)}) return vrs
def prime_varlists(self, keys=None): """Map primed `keys` to lists of primed variables. For each `k in keys`, add `"{k}'".format(k=k)` to `self.varlist`, with value the `list` that results from priming the variables in `self.varlist[k]`. If `keys is None`, prime all unprimed variable lists. """ if keys is None: keys = set(self.varlist) for k in keys: if stx.isprimed(k): continue pk = stx.prime(k) pvrs = stx.prime_vars(self.varlist[k]) self.varlist[pk] = pvrs
def assert_consistent(self, moore=True): """Assert that `init` and `win` contain state predicates.""" varlists = list(self.varlist.values()) assert pairwise_disjoint(varlists) for u in self.init.values(): assert sym_bdd.is_state_predicate(u) # Moore actions for player, action in self.action.items(): primed = { stx.unprime(var) for var in self.support(action) if stx.isprimed(var) } # applicable only after unzip # assert primed.issubset(self.varlist[player]), ( # (player, primed)) for d in self.win.values(): for v in d.values(): for u in v: assert sym_bdd.is_state_predicate(u)
def _add_bitnames(t): """Map each integer to a list of bit variables.""" for var, d in t.items(): if d['type'] != 'int': continue assert d['type'] == 'int', d['type'] if stx.isprimed(var): name = stx.unprime(var) prime = stx.PRIME else: name = var prime = '' bits = [ '{name}_{i}{prime}'.format( name=name, i=i, prime=prime) for i in range(d['width'])] are_booleans = list(filter(t.__contains__, bits)) assert not are_booleans, (bits, t) d['bitnames'] = bits
def _add_bitnames(t): """Map each integer to a list of bit variables.""" for var, d in t.items(): if d['type'] != 'int': continue assert d['type'] == 'int', d['type'] if stx.isprimed(var): name = stx.unprime(var) prime = stx.PRIME else: name = var prime = '' bits = [ '{name}_{i}{prime}'.format(name=name, i=i, prime=prime) for i in range(d['width']) ] are_booleans = list(filter(t.__contains__, bits)) assert not are_booleans, (bits, t) d['bitnames'] = bits
def plot_machines(asm): """Plot machine behaviors over a finite number of steps.""" nrows = len(asm.machines) history = asm.past + [asm.state] n_steps = len(history) # missing last state t = range(n_steps) plt.subplots(nrows=nrows, ncols=1) styles = ['b-o', 'r--', 'k-', 'g-*'] # def style picker for i, (name, stm) in enumerate(asm.machines.items()): plt.subplot(nrows, 1, i + 1) plt.title(name) styles_cp = list(styles) # TODO: could instead plot only sys vars and memory for var in stm.vars: if stx.isprimed(var): continue x = [steps.omit_prefix(state, name)[var] for state in history] style = styles_cp.pop() plt.plot(t, x, style, label=var) plt.legend() plt.grid()
def is_primed_state_predicate(u, fol): """Return `True` if `u` depends only on primed variables. Only constant parameters (rigid variables) can appear unprimed in `u`. Any flexible variables in `u` should be primed. An identifier that is declared only unprimed is assumed to be a rigid variables. If a primed sibling is declared, then the identifier is assumed to be a flexible variable. """ support = fol.support(u) primed = {name for name in support if stx.isprimed(name)} unprimed = support - primed any_flexible = False for name in unprimed: primed = stx.prime(name) if primed in fol.vars: any_flexible = True break return not any_flexible
def action_to_steps(aut, qinit='\A \A'): r"""Return enumerated graph with steps as edges. Only `aut.init['env']` considered. The predicate `aut.init['sys']` is ignored. `qinit` has different meaning that in `omega.games.gr1`. Nonetheless, for synthesized `aut.init['env']`, the meaning of `qinit` here yields the expected result. Enumeration is done based on `qinit`: - `'\A \A'`: pick all states that satisfy `aut.init['env']` - `'\E \E'`: pick one state that satisfies `aut.init['env']` - `'\A \E'`: for all states that satisfy `aut.init['env']`, pick a unique state for each env state `x` - `'\E \A'`: pick a sys state `u` and enumerate all states that satisfy `aut.init['env']` and `y = u` """ assert aut.action['sys'] != aut.false primed_vars = _primed_vars_per_quantifier(aut.varlist) unprime_vars = {stx.prime(var): var for var in aut.vars if not stx.isprimed(var)} # fix an order for tupling keys = list(k for k in aut.vars if not stx.isprimed(k)) umap = dict() # map assignments -> node numbers g = nx.DiGraph() queue, visited = _init_search(g, aut, umap, keys, qinit) g.initial_nodes = set(queue) varnames = set(keys) symbolic._assert_support_moore(aut.action['sys'], aut) # search while queue: node = queue.pop() values = g.node[node] log.debug('at node: {d}'.format(d=values)) assert set(values) == varnames, (values, aut.vars) u = aut.action['env'] u = aut.let(values, u) # apply Mealy controller function env_iter = aut.pick_iter( u, care_vars=primed_vars['env']) u = aut.action['sys'] assert u != aut.false sys = aut.let(values, u) assert sys != aut.false for next_env in env_iter: log.debug('next_env: {r}'.format(r=next_env)) # no effect if `aut.moore` u = aut.let(next_env, sys) u = aut.let(unprime_vars, u) env_values = {unprime_vars[var]: value for var, value in next_env.items()} v = aut.let(env_values, visited) # prefer already visited nodes v &= u if v == aut.false: log.info('cannot remain in visited nodes') v = u remain = False else: remain = True assert v != aut.false sys_values = aut.pick( v, care_vars=aut.varlist['sys']) d = dict(env_values) d.update(sys_values) # assert u = aut.let(d, visited) assert u == aut.true or u == aut.false assert remain == (u == aut.true), remain # find or add node if remain: next_node = _find_node(d, umap, keys) else: next_node = _add_new_node(d, g, queue, umap, keys) visited = _add_to_visited(d, visited, aut) g.add_edge(node, next_node) log.debug(( 'next env: {e}\n' 'next sys: {s}\n').format( e=env_values, s=sys_values)) return g
def is_state_predicate(u): """Return `True` if `u` depends only on unprimed values.""" return not any(stx.isprimed(var) for var in u.support)
def is_proper_action(u): """Return `True` if `u` depends on both primed and unprimed.""" r = u.support return ( any(stx.isprimed(var) for var in r) and any(not stx.isprimed(var) for var in r))
def is_proper_action(u): """Return `True` if `u` depends on both primed and unprimed.""" r = u.support return (any(stx.isprimed(var) for var in r) and any(not stx.isprimed(var) for var in r))
def primed_support(u, fol): """Return primed identifiers that `u` depends on. These identifiers include both variables and constants. """ return {k for k in fol.support(u) if stx.isprimed(k)}
def unprimed_support(u, fol): """Return unprimed identifiers that `u` depends on.""" return {k for k in fol.support(u) if not stx.isprimed(k)}
def action_to_steps(aut, qinit='\A \A'): r"""Return enumerated graph with steps as edges. Only `aut.init['env']` considered. The predicate `aut.init['sys']` is ignored. `qinit` has different meaning that in `omega.games.gr1`. Nonetheless, for synthesized `aut.init['env']`, the meaning of `qinit` here yields the expected result. Enumeration is done based on `qinit`: - `'\A \A'`: pick all states that satisfy `aut.init['env']` - `'\E \E'`: pick one state that satisfies `aut.init['env']` - `'\A \E'`: for all states that satisfy `aut.init['env']`, pick a unique state for each env state `x` - `'\E \A'`: pick a sys state `u` and enumerate all states that satisfy `aut.init['env']` and `y = u` """ assert aut.action['sys'] != aut.false primed_vars = _primed_vars_per_quantifier(aut.varlist) unprime_vars = { stx.prime(var): var for var in aut.vars if not stx.isprimed(var) } # fix an order for tupling keys = list(k for k in aut.vars if not stx.isprimed(k)) umap = dict() # map assignments -> node numbers g = nx.DiGraph() queue, visited = _init_search(g, aut, umap, keys, qinit) g.initial_nodes = set(queue) varnames = set(keys) symbolic._assert_support_moore(aut.action['sys'], aut) # search while queue: node = queue.pop() values = g.nodes[node] log.debug('at node: {d}'.format(d=values)) assert set(values) == varnames, (values, aut.vars) u = aut.action['env'] u = aut.let(values, u) # apply Mealy controller function env_iter = aut.pick_iter(u, care_vars=primed_vars['env']) u = aut.action['sys'] assert u != aut.false sys = aut.let(values, u) assert sys != aut.false for next_env in env_iter: log.debug('next_env: {r}'.format(r=next_env)) # no effect if `aut.moore` u = aut.let(next_env, sys) u = aut.let(unprime_vars, u) env_values = { unprime_vars[var]: value for var, value in next_env.items() } v = aut.let(env_values, visited) # prefer already visited nodes v &= u if v == aut.false: log.info('cannot remain in visited nodes') v = u remain = False else: remain = True assert v != aut.false sys_values = aut.pick(v, care_vars=aut.varlist['sys']) d = dict(env_values) d.update(sys_values) # assert u = aut.let(d, visited) assert u == aut.true or u == aut.false assert remain == (u == aut.true), remain # find or add node if remain: next_node = _find_node(d, umap, keys) else: next_node = _add_new_node(d, g, queue, umap, keys) visited = _add_to_visited(d, visited, aut) g.add_edge(node, next_node) log.debug(('next env: {e}\n' 'next sys: {s}\n').format(e=env_values, s=sys_values)) return g