def snakes_find_the_head(g): # We'll want a type that represents a cell in the grid. We use a datatype because we can make it a sum type - # that is, its 0-arity constructors represent a closed enumeration of distinct objects. Cell = Datatype("Cell") for x, y in g.coords(): if g.snake(x, y) is not None: Cell.declare("cell_{}_{}".format(x, y)) Cell = Cell.create() # We'll have two functions that we explicitly declare. One is the short-distance "connected" relationship. # The other is a convenience function for turning the coordinates of a cell into the datatype member that # represents that position. Connected = Function("Connected", Cell, Cell, BoolSort()) XYToCell = Function("XYToCell", IntSort(), IntSort(), Cell) cell = {} for x, y in g.coords(): if g.snake(x, y) is not None: cell[x, y] = getattr(Cell, "cell_{}_{}".format(x, y)) g.add(XYToCell(x, y) == cell[x, y]) # Two cells are connected *if and only* if they are adjacent and both hold snakes # We need to be really clear about the "and only if" part; a naive implementation here will let the # solver fill in other arbitrary values for `Connected` in order to make the desired outcome true. # We do this by ensuring there's a value declared for our Connected relationship between every pair # of potential arguments. for x1, y1 in g.coords(): c1 = g.snake(x1, y1) if c1 is None: continue # If there's a snake here, the cell is connected to itself g.add(Connected(cell[x1, y1], cell[x1, y1]) == c1) for x2, y2 in g.coords(): c2 = g.snake(x2, y2) if c2 is None or (x1, y1) == (x2, y2): continue if manh(x1, y1, x2, y2) == 1: g.add(Connected(cell[x1, y1], cell[x2, y2]) == And(c1, c2)) else: # Without this, our function declaration is only partial. The solver can fill in missing values # in order to scupper our good intentions. g.add(Not(Connected(cell[x1, y1], cell[x2, y2]))) # The transitive closure of Connectedness is Reaches Reaches = TransitiveClosure(Connected) # For every cell in the grid, if it's a snake then we can connect it to the head position hx, hy = g.head() for x, y in g.coords(): c = g.snake(x, y) if c is None: continue g.add(Implies(c, Reaches(cell[x, y], XYToCell(hx, hy))))
def skyscraper(givens: Dict[Direction, List[int]]) -> str: """Solver for Skyscraper minipuzzles.""" sym = grilops.make_number_range_symbol_set(1, SIZE) sg = grilops.SymbolGrid(LATTICE, sym) shifter = Shifter(sg.solver) # Each row and each column contains each building height exactly once. for y in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)])) for x in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)])) # We'll use the sightlines accumulator to keep track of a tuple storing: # the tallest building we've seen so far # the number of visible buildings we've encountered Acc = Datatype("Acc") # pylint: disable=C0103 Acc.declare("acc", ("tallest", IntSort()), ("num_visible", IntSort())) Acc = Acc.create() # pylint: disable=C0103 def accumulate(a, height): return Acc.acc( If(height > Acc.tallest(a), height, Acc.tallest(a)), If(height > Acc.tallest(a), Acc.num_visible(a) + 1, Acc.num_visible(a))) for d, gs in givens.items(): for i, g in enumerate(gs): if d.vector.dy != 0: g = g - shifter.col_shifts.get(i, 0) p = Point(0 if d.vector.dy < 0 else SIZE - 1, i) elif d.vector.dx != 0: g = g - shifter.row_shifts.get(i, 0) p = Point(i, 0 if d.vector.dx < 0 else SIZE - 1) sg.solver.add(g == Acc.num_visible( # type: ignore[attr-defined] grilops.sightlines.reduce_cells( sg, p, LATTICE.opposite_direction(d), Acc.acc(0, 0), # type: ignore[attr-defined] accumulate))) assert sg.solve() sg.print() print() shifter.print_shifts() print() return shifter.eval_binary()
def __init__(self, template, encoder, encoder_info, spec): self._template = template self._spec = spec self._encoder = encoder self._encoder_info = encoder_info # state self.state_type = Datatype('T%d' % template.template_index) for i in range(0, template.bound): self.state_type.declare('t%d_%d' % (template.template_index, i)) self.state_sort = self.state_type.create() # output functions self.output_functions = [Function(str(output_signal), self.state_sort, BoolSort()) for output_signal in template.outputs] logging.debug("Output functions for template %d: %s", template.template_index, self.output_functions) self.guard_function = None # guard function: t, I, t' -> g [BitVec(Sum_i |Ti|]) self._define_guard() # transition enabled self.is_enabled = None self._define_is_enabled() # any enabled self.is_any_enabled = None self._define_is_any_enabled() # lazy initialization self._guard_set = None self.state_guard = None self._define_state_guard() # declare function self.delta_enabled_functions = [self._get_delta_enabled_function(i) for i in range(0, template.cutoff)]
def main(): """Skyscraper solver example.""" lattice = grilops.get_square_lattice(SIZE) sg = grilops.SymbolGrid(lattice, SYM) # Each row and each column contains each building height exactly once. for y in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)])) for x in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)])) # We'll use the sightlines accumulator to keep track of a tuple storing: # the tallest building we've seen so far # the number of visible buildings we've encountered Acc = Datatype("Acc") # pylint: disable=C0103 Acc.declare("acc", ("tallest", IntSort()), ("num_visible", IntSort())) Acc = Acc.create() # pylint: disable=C0103 def accumulate(a, height): return Acc.acc( If(height > Acc.tallest(a), height, Acc.tallest(a)), If(height > Acc.tallest(a), Acc.num_visible(a) + 1, Acc.num_visible(a))) for x, c in enumerate(GIVEN_TOP): sg.solver.add(c == Acc.num_visible( grilops.sightlines.reduce_cells(sg, Point(0, x), Vector(1, 0), Acc.acc(0, 0), accumulate))) for y, c in enumerate(GIVEN_LEFT): sg.solver.add(c == Acc.num_visible( grilops.sightlines.reduce_cells(sg, Point(y, 0), Vector(0, 1), Acc.acc(0, 0), accumulate))) for y, c in enumerate(GIVEN_RIGHT): sg.solver.add(c == Acc.num_visible( grilops.sightlines.reduce_cells(sg, Point( y, SIZE - 1), Vector(0, -1), Acc.acc(0, 0), accumulate))) for x, c in enumerate(GIVEN_BOTTOM): sg.solver.add(c == Acc.num_visible( grilops.sightlines.reduce_cells(sg, Point( SIZE - 1, x), Vector(-1, 0), Acc.acc(0, 0), accumulate))) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
from .meyer import U from .util.z3py_set import Set from .util.z3py_rel import Rel from .util.z3py_util import const, consts, show_record_element ## @file program.py # This module can be used for definition of specification/program instance # # SET = ArraySort(U, BoolSort()) # OOPSet = ArraySort(IntSort(), ArraySort(U, BoolSort())) PRE = ArraySort(U, BoolSort()) POST = ArraySort(U, ArraySort(U, BoolSort())) PROG = Datatype('Prog') PROG.declare('mk_prog', ('set', SET), ('pre', PRE), ('post', POST)) PROG = PROG.create() set_ = PROG.set pre_ = PROG.pre post_ = PROG.post class Program(): """Base class for Program instance.""" # @param p A program instance created by Z3.py. def __init__(self, p): self.p = p # @param x An element that is included in Set of this program.
def encode_automaton(self, automaton, automaton_index, is_architecture_specific, cutoff, global_cutoff): ''' Encodes the given automaton :param automaton: Automaton instance :param automaton_index: Automaton index used for identifying the lambda function corresponding to the automaton :param is_architecture_specific: Whether automaton is required by the used architecture :param cutoff: Cut-off associated with the current automaton :param global_cutoff: Maximum of all automata-specific cut-offs ''' # declare UCT state uct_state = Datatype('Q_%d' % automaton_index) state_prefix = 'q%d_' % automaton_index nodes_list = list(automaton.nodes) for node in nodes_list: uct_state.declare(state_prefix + node.name) uct_state = uct_state.create() uct_states_dict = {nodes_list[i].name: getattr(uct_state, uct_state.constructor(i).name()) for i in range(len(nodes_list))} # declare lambda functions lambda_b_function_argument_sorts = \ [uct_state] + \ self.get_fresh_global_state_sorts(cutoff=cutoff) + \ [BoolSort()] lambda_b_function = Function('lambda_b_%d' % (automaton_index), lambda_b_function_argument_sorts) lambda_s_function_argument_sorts = \ [uct_state] + \ self.get_fresh_global_state_sorts(cutoff=cutoff) + \ [IntSort()] lambda_s_function = Function('lambda_s_%d' % (automaton_index), lambda_s_function_argument_sorts) # avoid global deadlocks in case of the fairness property if is_architecture_specific: self._avoid_deadlocks(uct_state, lambda_b_function, cutoff, global_cutoff) assert(len(automaton.initial_sets_list) == 1) initial_uct_states = [uct_states_dict[node.name] for node in automaton.initial_sets_list[0]] initial_system_states = self._get_initial_system_states(cutoff=cutoff) # list of tuples in format (q0, (t1, t2, ...)) initial_state_tuples = product(*[initial_uct_states, initial_system_states]) # merge tuples initial_state_tuples = [tuple([item[0]] + list(item[1])) for item in initial_state_tuples] logging.debug("Automaton %d Initial states: %s", automaton_index, initial_state_tuples) for initial_uct_state in initial_state_tuples: self.encoder_info.solver.add(lambda_b_function(initial_uct_state)) self.encoder_info.solver.add(lambda_s_function(initial_uct_state) == 0) # (template function, instance index) template_instance_index_tuples = \ self._get_templates_instance_index_tuples(cutoff=cutoff) # assignment of the scheduling variables the particular processes # are scheduled (k,i) -> scheduling variable assignment list schedule_values_dict = dict(self.spec.get_schedule_values()) scheduling_signals = self.spec.get_scheduling_signals() # used for SCC lambda_s optimization sccs = build_state_to_rejecting_scc(automaton) scc_lambda_functions = \ {scc: Function('lambda_s_%d_%d' % (automaton_index, scc_index), [uct_state] + self.get_fresh_global_state_sorts(cutoff=cutoff) + [BitVecSort(len(scc))]) for scc_index, scc in enumerate(sccs.values())} spec_cutoff_process_indices = \ self.get_process_indices(cutoff=self.spec.cutoff) global_state_tuples = \ self.get_fresh_global_state_variables(cutoff=cutoff, prefix="curr", include_indices=True) global_state_dict = dict(global_state_tuples) current_global_state = [state_variable for _, state_variable in global_state_tuples] input_signals_set = {(t[0].template_index, t[1]): t[0].get_input_signals(t[1]) for t in template_instance_index_tuples} input_signals_list = [sig for t in template_instance_index_tuples for sig in t[0].get_input_signals(t[1])] input_signal_expr_dict = {sig: Bool(str(sig)) for sig in input_signals_list} # dictionary of output signals -> function call output_signal_expr_dict = \ {signal_name: signal_function(global_state_dict[(template_function.template_index, instance_index)]) for template_function, instance_index in template_instance_index_tuples for signal_name, signal_function in template_function.get_output_signals_function_dict(instance_index).items()} function_placeholder_signals_set = \ set(self.architecture.get_placeholder_signals(cutoff)) transitions = [(src_node, transition, target_node_info) for src_node in automaton.nodes for transition, target_node_infos in src_node.transitions.items() for target_node_info in target_node_infos[0]] node = None for src_node, transition, target_node_info in transitions: target_node, is_rejecting_target_node = target_node_info logging.debug("Automaton: %d: %s->%s, condition: %s", automaton_index, src_node.name, target_node.name, transition) for templ_func, i in template_instance_index_tuples: # we use k for the template index and i for the instance index # as defined in the paper k = templ_func.template_index others_global_state_tuples = \ [(k_i, state_variable) for k_i, state_variable in filter(lambda k_i_state_tuple: k_i_state_tuple[0] != (k, i), global_state_tuples)] others_global_state = [state for _, state in others_global_state_tuples] current_local_state = global_state_dict[(k, i)] next_local_state = Const('t_next_%d_%d' % (k, i), templ_func.state_sort) next_global_state = [state if k_i != (k, i) else next_local_state for k_i, state in global_state_tuples] # get scheduling assignment if current instance is scheduled sched_assignment = schedule_values_dict[(k, i)] sched_assignment_dict = \ {scheduling_signals[i]: sched_assignment[i] for i in range(0, len(scheduling_signals))} logging.debug("\tinstance: (%d, %d) sched=%s", k, i, sched_assignment) # parameters for functions guard_set_call_expr = \ templ_func.guard_set( self._blowup_state_set(others_global_state_tuples, spec_cutoff_process_indices, (k, i))) # only add constraint if scheduling assignment # matches the label if not self._compare_scheduling(sched_assignment_dict, transition): logging.debug("\tSKIP %s->%s, condition: %s, scheduling=%s" % (src_node.name, target_node.name, transition, sched_assignment)) continue transition_keys = set(transition.keys()) used_input_signals = transition_keys.intersection(set(input_signals_list)) used_output_signals = transition_keys.intersection(output_signal_expr_dict.keys()) used_placeholder_signals = transition_keys.intersection(function_placeholder_signals_set) used_scheduler_signals = transition_keys.intersection(set(scheduling_signals)) assert(len(used_input_signals) + \ len(used_output_signals) + \ len(used_placeholder_signals) + \ len(used_scheduler_signals) == len(transition.items())) condition = [] for input_signal in used_input_signals: condition.append(input_signal_expr_dict[input_signal] == transition[input_signal]) for output_signal in used_output_signals: condition.append(output_signal_expr_dict[output_signal] == transition[output_signal]) for placeholder_signal in used_placeholder_signals: ph_instance = (placeholder_signal.template_index, placeholder_signal.instance_index) ph_relative_global_state_tuples = \ list(filter(lambda x: x[0] != ph_instance, global_state_tuples)) ph_template_func = self.encoder_info.template_functions[ placeholder_signal.template_index] ph_gs = ph_template_func.guard_set( self._blowup_state_set(ph_relative_global_state_tuples, spec_cutoff_process_indices, ph_instance)) ph_relative_current_local_state = global_state_dict[ph_instance] ph_relative_current_inputs = \ [input_signal_expr_dict[sig] for sig in input_signals_set[ph_instance]] if placeholder_signal.name.startswith('enabled'): condition.append(ph_template_func.is_any_enabled( [ph_relative_current_local_state] + \ ph_relative_current_inputs + \ [ph_gs]) == transition[placeholder_signal]) elif placeholder_signal.name.startswith('active'): condition.append(self.encoder_info.is_scheduled( [placeholder_signal.template_index, placeholder_signal.instance_index] + \ sched_assignment) == transition[placeholder_signal]) elif placeholder_signal.name.startswith('init'): req_initial_states = ph_template_func.get_initial_states() assert(len(req_initial_states) == 1) condition.append( (ph_relative_current_local_state == req_initial_states[0]) == transition[placeholder_signal]) else: raise Exception(placeholder_signal.name) condition_expression = True if len(condition) > 0: condition_expression = And(*condition) current_local_input_arguments = [input_signal_expr_dict[sig] for sig in input_signals_set[(k, i)]] input_arguments = [input_signal_expr_dict[signal] for signal in input_signals_list] forall_arguments = \ [current_local_state, next_local_state] + \ others_global_state + \ input_arguments current_combined_state_parameters = \ [uct_states_dict[src_node.name]] + \ current_global_state next_combined_state_parameters = \ [uct_states_dict[target_node.name]] + \ next_global_state delta_enabled_function_parameters = [current_local_state] + \ current_local_input_arguments + \ [next_local_state] + \ [guard_set_call_expr] lambda_s_req_expr = None if self._encoding_optimization & EncodingOptimization.LAMBDA_SCC: logging.debug("Use LAMBDA_SCC optimization") lambda_s_req_expr = True current_scc = sccs.get(src_node) if current_scc is not None and current_scc == sccs.get(target_node): scc_ls_func = scc_lambda_functions[current_scc] lambda_s_req_expr = \ [UGE, UGT][is_rejecting_target_node](scc_ls_func(next_combined_state_parameters), scc_ls_func(current_combined_state_parameters)) else: # using no lambda_s optimizations if is_rejecting_target_node: lambda_s_req_expr = ( lambda_s_function(next_combined_state_parameters) > lambda_s_function(current_combined_state_parameters)) else: lambda_s_req_expr = ( lambda_s_function(next_combined_state_parameters) >= lambda_s_function(current_combined_state_parameters)) extended_condition_expr = \ templ_func.delta_enabled_functions[i]( delta_enabled_function_parameters) extended_condition_expr = \ Or(extended_condition_expr, And(current_local_state == next_local_state, Not(templ_func.is_any_enabled( [current_local_state] + \ current_local_input_arguments + \ [guard_set_call_expr])))) expr = ForAll(forall_arguments, Implies( And(lambda_b_function(current_combined_state_parameters), condition_expression, extended_condition_expr), And(lambda_b_function(next_combined_state_parameters), lambda_s_req_expr))) logging.debug("\tADD %s->%s, condition: %s, scheduling=%s", src_node.name, target_node.name, transition, sched_assignment) self.encoder_info.solver.add(expr)
def test_datatype_payloads(self): lattice = get_square_lattice(3) sym = make_number_range_symbol_set(0, 2) row_grid = SymbolGrid(lattice, sym) col_grid = SymbolGrid(lattice, sym, solver=row_grid.solver) RowCol = Datatype("RowCol") RowCol.declare("row_col", ("row", IntSort()), ("col", IntSort())) RowCol = RowCol.create() sc = ShapeConstrainer(lattice, [ Shape([ (Vector(0, 0), RowCol.row_col(0, 0)), (Vector(0, 1), RowCol.row_col(0, 1)), (Vector(1, 0), RowCol.row_col(1, 0)), ]), Shape([ (Vector(0, 1), RowCol.row_col(0, 2)), (Vector(1, 0), RowCol.row_col(1, 1)), (Vector(1, 1), RowCol.row_col(1, 2)), (Vector(2, 1), RowCol.row_col(2, 2)), ]), Shape([ (Vector(0, 0), RowCol.row_col(2, 0)), (Vector(0, 1), RowCol.row_col(2, 1)), ]), ], solver=row_grid.solver, complete=True) for p in lattice.points: row_grid.solver.add( row_grid.cell_is(p, RowCol.row(sc.shape_payload_grid[p]))) col_grid.solver.add( col_grid.cell_is(p, RowCol.col(sc.shape_payload_grid[p]))) self.assertTrue(row_grid.solve()) solved_row_grid = row_grid.solved_grid() solved_col_grid = col_grid.solved_grid() for p in lattice.points: self.assertEqual(solved_row_grid[p], p.y) self.assertEqual(solved_col_grid[p], p.x) self.assertTrue(row_grid.is_unique())
class TemplateFunction(metaclass=ABCMeta): ''' Provides functionality for generating and adding template specific formulae to the SMT context ''' def __init__(self, template, encoder, encoder_info, spec): self._template = template self._spec = spec self._encoder = encoder self._encoder_info = encoder_info # state self.state_type = Datatype('T%d' % template.template_index) for i in range(0, template.bound): self.state_type.declare('t%d_%d' % (template.template_index, i)) self.state_sort = self.state_type.create() # output functions self.output_functions = [Function(str(output_signal), self.state_sort, BoolSort()) for output_signal in template.outputs] logging.debug("Output functions for template %d: %s", template.template_index, self.output_functions) self.guard_function = None # guard function: t, I, t' -> g [BitVec(Sum_i |Ti|]) self._define_guard() # transition enabled self.is_enabled = None self._define_is_enabled() # any enabled self.is_any_enabled = None self._define_is_any_enabled() # lazy initialization self._guard_set = None self.state_guard = None self._define_state_guard() # declare function self.delta_enabled_functions = [self._get_delta_enabled_function(i) for i in range(0, template.cutoff)] # self._define_optimizations() def _define_guard(self): """Defines the guard function guard: T, I, T -> BitVec(Sum_i |Ti|) t -- current state i -- multiple parameters corresponding to the inputs I t' -- successor state """ guard_function_params = [self.state_sort] + \ self._get_input_sorts() + \ [self.state_sort] + \ [BitVecSort(self._encoder_info.guard_size)] self.guard_function = Function(str('guard_%d' % self._template.template_index), guard_function_params) def _get_delta_enabled_function(self, instance_index): """Defines the function delta_enabled delta_enabled_i: T x I x T x S' -> Bool t, i, t -- transition identifying values s_s -- current global state set The scheduling must be ensured by an external function. """ function_arguments = self._get_guard_params_sorts() + \ [BitVecSort(self._encoder_info.guard_size)] + [BoolSort()] function_declaration = Function('delta_enabled_%d_%d' % (self._template.template_index, instance_index), function_arguments) t_current_variable = Const('tc', self.state_sort) t_next_variable = Const('tn', self.state_sort) input_variables = \ self._get_fresh_input_variables(instance_index=instance_index) guard_params = [t_current_variable] + \ input_variables + \ [t_next_variable] current_guard_set_parameter = BitVec('s', self._encoder_info.guard_size) function_parameters = guard_params + [current_guard_set_parameter] function_body = \ ForAll(function_parameters, function_declaration(function_parameters) == self._encoder_info.eval_guard(current_guard_set_parameter, self.guard_function(guard_params))) self._encoder_info.solver.add(function_body) return function_declaration def _define_is_any_enabled(self): """Defines the function is_any_enabled is_any_enabled checks if there exists at least one enabled transition for a given * current state * input values * global state set is_any_enabled: T x I x S' -> Bool t, i -- part of transition identifying values s_s -- global state set """ function_arguments = self._get_guard_params_sorts()[:-1] + \ [BitVecSort(self._encoder_info.guard_size), BoolSort()] # function declaration self.is_any_enabled = Function('is_any_enabled_%d' % (self._template.template_index), function_arguments) # function body guard_parameters = self.get_fresh_guard_params_variables()[:-1] global_state_parameter = BitVec('gs', self._encoder_info.guard_size) function_parameters = guard_parameters + [global_state_parameter] t_next = Const('tn', self.state_sort) function_body = \ ForAll(function_parameters, self.is_any_enabled(function_parameters) == Exists(t_next, self._encoder_info.eval_guard( global_state_parameter, self.guard_function(guard_parameters + [t_next])))) self._encoder_info.solver.add(function_body) def _define_is_enabled(self): ''' Defines the function is_enabled is_enabled checks if the transition identified by (current state, next state) is enabled for the given * input values * global state set is_any_enabled: T x I x T x S' -> Bool t, i, t -- current state, input, next state s_s -- global state set ''' function_arguments = \ self._get_guard_params_sorts() + \ [BitVecSort(self._encoder_info.guard_size), BoolSort()] # function declaration self.is_enabled = Function('is_enabled_%d' % (self._template.template_index), function_arguments) # function body guard_parameters = self.get_fresh_guard_params_variables() global_state_parameter = BitVec('gs', self._encoder_info.guard_size) function_parameters = guard_parameters + [global_state_parameter] function_body = \ ForAll(function_parameters, self.is_enabled(function_parameters) == self._encoder_info.eval_guard( global_state_parameter, self.guard_function(guard_parameters))) self._encoder_info.solver.add(function_body) @property def guard_set(self): ''' Guard set function ''' if self._guard_set is None: self.define_guard_set() return self._guard_set def define_guard_set(self): """ Defines the method that determines the relative global states guard set T1 T1 ... T1 T2 ... Tn -> BitVec """ function_arguments = \ self._encoder.get_fresh_global_state_sorts( absent_process=(self._template.template_index, 0)) + \ [BitVecSort(self._encoder_info.guard_size)] function_declaration = Function('guard_set_%d' % (self._template.template_index), function_arguments) self._guard_set = function_declaration # function body global_state_tuples_parameters = \ self._encoder.get_fresh_global_state_variables( absent_process=(self._template.template_index, 0)) global_state_tuples_indices = \ [k for _, k, _ in self._encoder.get_fresh_global_state_tuples( absent_process=(self._template.template_index, 0))] function_parameters = global_state_tuples_parameters assert(len(global_state_tuples_parameters) == len(global_state_tuples_indices)) bit_sum_expr = \ reduce(lambda x, y: x | y, [self._encoder_info.template_functions [global_state_tuples_indices[i]].state_guard( global_state_tuples_parameters[i]) for i in range(0, len(global_state_tuples_indices))]) function_body = ForAll(function_parameters, function_declaration(function_parameters) == bit_sum_expr) self._encoder_info.solver.add(function_body) def _get_guard_params_sorts(self): ''' Returns the guard parameter sorts T_k x I_k x T_k where I_k is Bool x Bool x ... x Bool (depending on number of inputs) ''' return [self.state_sort] + self._get_input_sorts() + [self.state_sort] def get_fresh_guard_params_variables(self): ''' Returns new guard parameter variables (tc, i_k, tn) of T_k x I_k x T_k The variable names are tc, tn, and inputs as defined by the signal names ''' return [Const('tc', self.state_sort)] + \ self._get_fresh_input_variables() + \ [Const('tn', self.state_sort)] def _get_fresh_input_variables(self, instance_index=None, sfx_pfx=""): ''' Gets fresh input variables i_k of I_k (i.e., Boolean variables of Boolean) The signal name can be extended by a suffix that consists of the parameter sfx_pfx concatenated with an optional instance index. By default, the variable name is equal to the signal name. :param instance_index: :param sfx_pfx: ''' suffix = sfx_pfx or "" + ("" if instance_index is None \ else "_" + str(instance_index)) return [Bool(str(input_signal) + suffix) for input_signal in self._template.inputs] def get_fresh_input_variables(self, prefix=None): ''' Returns fresh input variables :todo: TODO: clear, improve! instance_index can also be some suffix string :param prefix: ''' return self._get_fresh_input_variables(instance_index=prefix) def get_fresh_input_assignments(self): ''' Returns a tuple of possible assignments for each input variable ''' return [(False, True)] * len(self._template.inputs) def _get_input_sorts(self): ''' Returns a list of sorts used for the SMT representation of the template's input signals ''' return [BoolSort(), ] * len(self._template.inputs) def get_initial_states(self): ''' Returns Z3 consts that represent initial states of the given template ''' return [getattr(self.state_sort, self.state_sort.constructor(i).name()) for i in range(len(self._template.initial_states))] def get_input_signals(self, instance_index=None): ''' Returns input signals for a particular instance or quantified signals Returns the input signals for a single instance of the particular template or the quantified template signals if no instance index is provided :param instance_index: ''' if instance_index is None: return self._template.inputs return [template_input.get_instance_signal(instance_index) for template_input in self._template.inputs] def get_output_signals_function_dict(self, instance_index): """ Returns output signal mapped to SMT functions Returns a dictionary of signal name -> SMT function for all instantiated signal names corresponding to template signals """ return {self._template.outputs[i].get_instance_signal(instance_index): self.output_functions[i] for i in range(0, len(self.output_functions))} def _define_optimizations(self): # add state order idx_state_func = Function('state_index%d' % self.template_index, IntSort(), self.state_sort) state_indices = [(getattr(self.state_sort, self.state_sort.constructor(i).name()), i) for i in range(0, self._template.bound)] state_order_conjuncts = \ [idx_state_func(index) == state for state, index in state_indices] self._encoder_info.solver.add(And(state_order_conjuncts)) # if t_i is connected some t_{j} with j>=i+2, then there # must be a connection from t_i to t_{i+1} or there is some # other incoming transition for the particular node idx_curr_var = Int('idx_curr') idx_next_var = Int('idx_next') input_vars = self.get_fresh_input_variables() guard_params = [idx_state_func(idx_curr_var)] + input_vars + \ [idx_state_func(idx_next_var)] exists_transition2 = \ Exists( input_vars + [idx_next_var], And(idx_next_var > idx_curr_var + 1, (self.guard_function(guard_params) != BitVecVal(0, self._encoder_info.guard_size)))) guard_params = [idx_state_func(idx_curr_var)] + input_vars + \ [idx_state_func(idx_curr_var + 1)] exists_transition1 = \ Exists( input_vars, (self.guard_function(guard_params) != BitVecVal(0, self._encoder_info.guard_size))) input_vars_other = self.get_fresh_input_variables(prefix="other") idx_other_var = Int('idx_other') guard_params = [idx_state_func(idx_other_var)] + input_vars_other + \ [idx_state_func(idx_curr_var + 1)] exists_transition1_other = \ Exists( [idx_other_var] + input_vars_other, And(idx_other_var != idx_curr_var + 1, self.guard_function(guard_params) != BitVecVal(0, self._encoder_info.guard_size))) # self._encoder_info.solver.add( # ForAll([idx_curr_var], # Implies( # And([idx_curr_var >= 0, # idx_curr_var < self._template.bound - 2]), # Implies(exists_transition2, # Or(exists_transition1, exists_transition1_other))))) @property def template_index(self): return self._template.template_index @abstractmethod def _define_state_guard(self): pass
"""Z3 implementation of L - the meta-Kappa devised by Adrien Husson and Jean Krivine - using the Python bindings for Z3. """ from z3 import Datatype, ArraySort, IntSort, BoolSort, Function, Const from z3_helpers import Iff, Equals # Node is a datatype representing a vertex or node in a Kappa graph. Node = Datatype('Node') Node.declare('node', ('unique_identifier', IntSort())) Node = Node.create() # A datatype for storing a pair of edges Edge = Datatype('Edge') Edge.declare('edge', ('node1', Node), ('node2', Node)) Edge = Edge.create() Nodeset = ArraySort(Node, BoolSort()) Edgeset = ArraySort(Edge, BoolSort()) Labelset = ArraySort(IntSort(), BoolSort()) Labelmap = ArraySort(Node, Labelset) # Graph, before a rule or action has applied. Merged Pregraph and Postgraph # into a single datatype. Graph = Datatype('Graph') Graph.declare('graph', ('has', Nodeset), ('links', Edgeset), ('parents', Edgeset), ('labelmap', Labelmap)) Graph = Graph.create() # Atomic action. An Action is comprised of a set of these.
HEIGHT, WIDTH = 6, 10 LATTICE = grilops.get_rectangle_lattice(HEIGHT, WIDTH) SYM = grilops.make_letter_range_symbol_set("A", "Z") class Color(IntEnum): """The color of a section of a piece.""" YELLOW = 1 BLUE = 2 RED = 3 WHITE = 4 LetterColor = Datatype("LetterColor") LetterColor.declare("letter_color", ("letter", IntSort()), ("color", IntSort())) LetterColor = LetterColor.create() def letter_color(letter: str, color: Color) -> ExprRef: """Creates a LetterColor z3 constant.""" return LetterColor.letter_color(SYM[letter], color.value) # type: ignore[attr-defined] SHAPES: List[Shape] = [ Shape([ (Vector(0, 1), letter_color("R", Color.BLUE)), (Vector(1, 0), letter_color("R", Color.BLUE)),
from z3 import (BoolSort, Const, Datatype, Exists, ForAll, Function, Implies, Not, Solver, unsat) problem = """Someone who lived in Dreadbury Mansion killed Aunt Agatha. Agatha, the Butler and Charles were the only people who lived in Dreadbury Mansion. A killer always hates his victim, and is never richer than his victim. Charles hates no one that aunt Agatha hates. Agatha hates everyone except the butler. The butler hates everyone not richer than Aunt Agatha. The butler also hates everyone Agatha hates. No one hates everyone. Agatha is not the butler. Who killed Aunt Agatha? """ print(problem) # declare finite data type mansion MansionDT = Datatype("Mansion") MansionDT.declare("Agatha") MansionDT.declare("Butler") MansionDT.declare("Charles") # create finite sort Mansion Mansion = MansionDT.create() # constants for ease of reference a, b, c = Mansion.Agatha, Mansion.Butler, Mansion.Charles # declare predicates killed = Function("killed", Mansion, Mansion, BoolSort()) hates = Function("hates", Mansion, Mansion, BoolSort()) richer = Function("richer", Mansion, Mansion, BoolSort())
"""Z3 implementation of L - the meta-Kappa devised by Adrien Husson and Jean Krivine - using the Python bindings for Z3. """ from z3 import Datatype, ArraySort, IntSort, BoolSort, Function, Const from z3_helpers import Iff, Equals # Node is a datatype representing a vertex or node in a Kappa graph. Node = Datatype("Node") Node.declare("node", ("unique_identifier", IntSort())) Node = Node.create() # A datatype for storing a pair of edges Edge = Datatype("Edge") Edge.declare("edge", ("node1", Node), ("node2", Node)) Edge = Edge.create() Nodeset = ArraySort(Node, BoolSort()) Edgeset = ArraySort(Edge, BoolSort()) Labelset = ArraySort(IntSort(), BoolSort()) Labelmap = ArraySort(Node, Labelset) # Graph, before a rule or action has applied. Merged Pregraph and Postgraph # into a single datatype. Graph = Datatype("Graph") Graph.declare("graph", ("has", Nodeset), ("links", Edgeset), ("parents", Edgeset), ("labelmap", Labelmap)) Graph = Graph.create() # Atomic action. An Action is comprised of a set of these.