def rename_variables(let, u, aut): """Rename primed and unprimed occurrences of variables. @param let: `dict` that renames unprimed variable identifiers """ let_primed = {stx.prime(k): stx.prime(v) for k, v in let.items()} let.update(let_primed) r = aut.let(let, u) support = aut.support(r) assert not support.intersection(let), support return r
def _primed_vars_per_quantifier(varlist): """Return `dict` that maps each player to set of primed vars. @return: `dict` that maps player name -> set of primed vars """ assert 'env' in varlist, varlist assert 'sys' in varlist, varlist primed_vars = dict( env={stx.prime(var) for var in varlist['env']}, sys={stx.prime(var) for var in varlist['sys']}) return primed_vars
def _action_to_steps(aut, qinit): assert aut.action['sys'] != aut.false primed_vars = _primed_vars_per_quantifier(aut.varlist) vrs = set(aut.varlist['env']).union(aut.varlist['sys']) unprime_vars = {stx.prime(var): var for var in vrs} # fix an order for tupling keys = list(vrs) 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, varnames) 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) v, remain = _select_candidate_nodes( u, v, aut, visited=True) 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 _add_primed_bits(unprimed_bits): """Return list of ordered primed and unprimed bits.""" bits = list() for bit in unprimed_bits: primed = stx.prime(bit) bits.append(bit) bits.append(primed) return bits
def _prime_bits_of_integers(ints, t): """Return bit priming for integers in `x`.""" bit_rename = dict() for i in ints: bits = t.vars[i]['bitnames'] d = {k: stx.prime(k) for k in bits} bit_rename.update(d) return bit_rename
def _add_primed_bits(unprimed_bits): """Return list of ordered primed and unprimed bits.""" bits = list() for bit in unprimed_bits: primed = stx.prime(bit) bits.append(bit) bits.append(primed) return bits
def _prime_bits_of_integers(ints, t): """Return bit priming for integers in `x`.""" bit_rename = dict() for i in ints: bits = t.vars[i]['bitnames'] d = {k: stx.prime(k) for k in bits} bit_rename.update(d) return bit_rename
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 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 _prime_and_order_table(t): """Return ordered table of primed and unprimed variables. The table maps each (integer or Boolean) variable to a `dict` of attributes (same as `t`). The attributes include `"bitnames"`. The following attributes are added: - level: index among ordered prime and unprimed integers - len: number of values the variable can take @param t: table of unprimed variables as `str` @type t: `dict` @rtype: `dict` from `str` to `dict` """ ordvars = natsort.natsorted(t) dvars = dict() for i, var in enumerate(ordvars): d = t[var] j = 2 * i dtype = d['type'] if dtype in ('int', 'saturating'): bits = list(d['bitnames']) elif dtype == 'bool': bits = [var] m = 2**len(bits) primed = stx.prime(var) pbits = [stx.prime(bit) for bit in bits] k = j + 1 # copy attr dvars[var] = copy.deepcopy(d) dvars[primed] = copy.deepcopy(d) # update attr dvars[var].update(level=j, len=m, bitnames=bits) dvars[primed].update(level=k, len=m, bitnames=pbits) return dvars
def _prime_and_order_table(t): """Return ordered table of primed and unprimed variables. The table maps each (integer or Boolean) variable to a `dict` of attributes (same as `t`). The attributes include `"bitnames"`. The following attributes are added: - level: index among ordered prime and unprimed integers - len: number of values the variable can take @param t: table of unprimed variables as `str` @type t: `dict` @rtype: `dict` from `str` to `dict` """ ordvars = natsort.natsorted(t) dvars = dict() for i, var in enumerate(ordvars): d = t[var] j = 2 * i dtype = d['type'] if dtype in ('int', 'saturating'): bits = list(d['bitnames']) elif dtype == 'bool': bits = [var] m = 2**len(bits) primed = stx.prime(var) pbits = [stx.prime(bit) for bit in bits] k = j + 1 # copy attr dvars[var] = copy.deepcopy(d) dvars[primed] = copy.deepcopy(d) # update attr dvars[var].update(level=j, len=m, bitnames=bits) dvars[primed].update(level=k, len=m, bitnames=pbits) return dvars
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 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 replace_with_primed(self, vrs, u): """Substitute primed for unprimed `vrs` in `u`. For example: ``` u = aut.add_expr("x /\ y /\ z") vrs = ['x', 'y'] v = aut.replace_with_primed(vrs, u) v_ = aut.add_expr("x' /\ y' /\ z") assert v == v_ ``` """ let = {k: stx.prime(k) for k in vrs} return self.let(let, u)
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 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 replace_with_primed(self, vrs, u): r"""Substitute primed for unprimed `vrs` in `u`. For example: ``` u = aut.add_expr("x /\ y /\ z") vrs = ['x', 'y'] v = aut.replace_with_primed(vrs, u) v_ = aut.add_expr("x' /\ y' /\ z") assert v == v_ ``` Identifiers in `vrs` should be declared as variables. Identifiers declared as constants will raise errors. """ let = {k: stx.prime(k) for k in vrs} return self.let(let, u)
def replace_with_primed(self, vrs, u): r"""Substitute primed for unprimed `vrs` in `u`. For example: ``` u = aut.add_expr("x /\ y /\ z") vrs = ['x', 'y'] v = aut.replace_with_primed(vrs, u) v_ = aut.add_expr("x' /\ y' /\ z") assert v == v_ ``` Identifiers in `vrs` should be declared as variables. Identifiers declared as constants will raise errors. """ let = {k: stx.prime(k) for k in vrs} return self.let(let, u)
def _partition_vars(bits, ubits, ebits): """Return primed and unprimed variable names. @param bits: `list` of variable names as `str` @param ubits: universally quantified variables @param ebits: existentially quantified variables @return: (prime, partition) - prime: `dict` that maps unprimed to primed variable names - partition: `dict(uvars=set, upvars=set, evars=set, epvars=set)` """ prime = {b: stx.prime(b) for b in bits} partition = dict( uvars=set(ubits), upvars={prime[b] for b in ubits}, evars=set(ebits), epvars={prime[b] for b in ebits}) return prime, partition
def _partition_vars(bits, ubits, ebits): """Return primed and unprimed variable names. @param bits: `list` of variable names as `str` @param ubits: universally quantified variables @param ebits: existentially quantified variables @return: (prime, partition) - prime: `dict` that maps unprimed to primed variable names - partition: `dict(uvars=set, upvars=set, evars=set, epvars=set)` """ prime = {b: stx.prime(b) for b in bits} partition = dict(uvars=set(ubits), upvars={prime[b] for b in ubits}, evars=set(ebits), epvars={prime[b] for b in ebits}) return prime, partition
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 _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 _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 replace_with_unprimed(self, vrs, u): """Substitute unprimed `vrs` for primed in `u`.""" let = {stx.prime(k): k for k in vrs} return self.let(let, u)
def _prime_dict(d): """Return `dict` with primed keys and `dict` mapping to them.""" p = dict((stx.prime(k), d[k]) for k in d) d2p = {k: stx.prime(k) for k in d} return p, d2p
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 replace_with_unprimed(self, vrs, u): """Substitute unprimed `vrs` for primed in `u`.""" let = {stx.prime(k): k for k in vrs} return self.let(let, u)
def is_variable(name, fol): """Return `True` if `name` is declared as variable. The identifier `name` should be unprimed. """ return stx.prime(name) in fol.vars
def prime_vars(self, vrs): """Return `list` of primed variables from `vrs`.""" return [stx.prime(var) for var in vrs]
def _prime_dict(d): """Return `dict` with primed keys and `dict` mapping to them.""" p = dict((stx.prime(k), d[k]) for k in d) d2p = {k: stx.prime(k) for k in d} return p, d2p
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
def is_variable(name, fol): """Return `True` if `name` is declared as variable. The identifier `name` should be unprimed. """ return stx.prime(name) in fol.vars
def hide_vars_from_sys(vrs, inv, sys_player, aut): """Return new `sys_player` action, after hiding `vrs`. The variables `vrs` to be hidden are already selected. So this function is suitable for generating the component specifications after the parametric analysis has been completed. """ turn_type = aut.vars[TURN]['type'] turn_p = stx.prime(TURN) assert turn_type == 'int', turn_type turn_dom = aut.vars[TURN]['dom'] # extract full-info actions from assembly action action = sym.conj_actions_of(aut.players, aut) inv_p = aut.replace_with_primed(aut.vars_of_all_players, inv) assembly_next = inv & inv_p & action others = set(aut.players) others.remove(sys_player) # notice the symmetry sys_vars = aut.vars_of_players([sys_player]) sys_vars_p = aut.prime_vars(sys_vars) env_vars = aut.vars_of_players(others) env_vars_p = aut.prime_vars(env_vars) extracted_sys_next = aut.exist(env_vars_p, assembly_next) extracted_env_next = aut.exist(sys_vars_p, assembly_next) # decompile `SysStep` k = aut.players[sys_player] kp = increment_turn(k, turn_dom) u = aut.let({TURN: k}, extracted_sys_next) inv_proj = aut.let({TURN: k}, inv) v = aut.let({turn_p: kp}, inv_p) inv_p_proj = aut.exist(env_vars_p, v) care = inv_proj & inv_p_proj s = sym.dumps_expr(u, aut, care=care) print('ExtractedSysStep ==') print(s) # hide variables from `SysNext` sys_next = extracted_sys_next u = sys_next | ~inv u = aut.forall(vrs, u) inv_h = aut.exist(vrs, inv) simpler_sys_next = u & inv_h # hide variables from `EnvNext` env_next = extracted_env_next u = env_next & inv vrs_p = aut.prime_vars(vrs) qvars = set(vrs).union(vrs_p) simpler_env_next = aut.exist(qvars, u) # decompile `inv_h` s = sym.dumps_expr(inv_h, aut, use_types=True) print('InvH == \E h: Inv <=>') print(s) # decompile `SimplerSysNext` k = aut.players[sys_player] u = aut.let({TURN: k}, simpler_sys_next) inv_h_p = aut.replace_with_primed(aut.vars_of_all_players, inv_h) inv_h_p = aut.exist(env_vars_p, inv_h_p) assert stx.prime(TURN) not in aut.support(inv_h_p) inv_h_proj = aut.let({TURN: k}, inv_h) care = inv_h_proj & inv_h_p s = sym.dumps_expr(u, aut, care=inv_h, use_types=True) print('SimplerSysNext ==') print(s) # decompile `SimplerEnvNext` inv_h_p = aut.replace_with_primed(aut.vars_of_all_players, inv_h) inv_h_p = aut.exist(sys_vars_p, inv_h_p) for player, k in aut.players.items(): if player == sys_player: continue if k is None: print('Scheduler skipped (plays concurrently)') continue inv_h_proj = aut.let({TURN: k}, inv_h) s = sym.dumps_expr(inv_h_proj, aut, use_types=True) print('InvH{player} == '.format(player=player)) print(s) # use (known) scheduler action as care set kp = increment_turn(k, turn_dom) scheduler_action = {TURN: k, turn_p: kp} u = aut.let(scheduler_action, simpler_env_next) inv_h_p_proj = aut.let({turn_p: kp}, inv_h_p) care = inv_h_proj & inv_h_p_proj s = sym.dumps_expr(u, aut, care=inv_h, use_types=True) print('Simpler{player}Next == '.format(player=player)) print(s)