class Y2021D24(object): _re_inp = re.compile(r'inp ([wxyz])') _re_add = re.compile(r'add ([wxyz]) ([wxyz]|-?\d+)') _re_mul = re.compile(r'mul ([wxyz]) ([wxyz]|-?\d+)') _re_div = re.compile(r'div ([wxyz]) ([wxyz]|-?\d+)') _re_mod = re.compile(r'mod ([wxyz]) ([wxyz]|-?\d+)') _re_eql = re.compile(r'eql ([wxyz]) ([wxyz]|-?\d+)') def __init__(self, file_name): self.solver = Optimize() inputs = [Int(f'model_{i}') for i in range(14)] self.solver.add([i >= 1 for i in inputs]) self.solver.add([i <= 9 for i in inputs]) # Please don't ask me to explain this. There's a common pattern in the input code that treats z like a number # of base 26 and the operations are either right shift or left shift on that number +- some value. self.solver.add(inputs[0] + 6 - 6 == inputs[13]) self.solver.add(inputs[1] + 11 - 6 == inputs[12]) self.solver.add(inputs[2] + 5 - 13 == inputs[11]) self.solver.add(inputs[3] + 6 - 8 == inputs[8]) self.solver.add(inputs[4] + 8 - 1 == inputs[5]) self.solver.add(inputs[6] + 9 - 16 == inputs[7]) self.solver.add(inputs[9] + 13 - 16 == inputs[10]) my_sum = IntVal(0) for index in range(len(inputs)): my_sum = (my_sum * 10) + inputs[index] self.value = Int('value') self.solver.add(my_sum == self.value) def part1(self): self.solver.push() self.solver.maximize(self.value) self.solver.check() result = self.solver.model()[self.value] self.solver.pop() print("Part 1:", result) def part2(self): self.solver.push() self.solver.minimize(self.value) self.solver.check() result = self.solver.model()[self.value] self.solver.pop() print("Part 2:", result)
def _solve( solver: z3.Optimize, channels: List[Channel], devices: List[Device] ) -> Tuple[Problem, z3.Model]: problem = _problem(solver, freqs=[c.frequency for c in channels], devices=devices) if solver.check() != z3.sat: # TODO: consider getting an unsat core raise ValueError(f"No valid assignment possible, add more devices") # find the minimal number of devices that cover all frequencies # it is faster to do this iteratively than to offload these to # smt constraints. do this in reverse so we end up assigning # the lowest numbered devices for r in problem.ranges[::-1]: # to control the device placements adjust the order they're eliminated from # the solution. for example, this will prefer devices that have a smaller # minimum sample rate over devices with a larger one: # for r, _ in zip(problem.ranges, problem.devices), key=lambda x: -x[1].min_sample_rate solver.push() solver.add(r == 0) if solver.check() != z3.sat: solver.pop() break devices_required = z3.Sum([z3.If(r > 0, 1, 0) for r in problem.ranges]) # minimize the sum of frequency ranges solver.minimize(z3.Sum(*problem.ranges)) # and the frequency, just to produce deterministic results solver.minimize(z3.Sum(*problem.lower_freq)) assert solver.check() == z3.sat model = solver.model() print(f"Devices required: {model.eval(devices_required)}", file=sys.stderr) return problem, model
def main(args): # print(args) seed = int(args[0]) random.seed(seed) # X is a three dimensional grid containing (t, x, y) X = [[[Bool("x_%s_%s_%s" % (k, i, j)) for j in range(GRID_SZ)] for i in range(GRID_SZ)] for k in range(HOPS + 1)] s = Optimize() # Initial Constraints s.add(X[0][0][0]) s.add([Not(cell) for row in X[0] for cell in row][1:]) # Final constraints s.add(X[HOPS][GRID_SZ - 1][GRID_SZ - 1]) s.add([Not(cell) for row in X[HOPS] for cell in row][:-1]) #Sanity Constraints for grid in X: for i in range(len(grid)): for j in range(len(grid)): for p in range(len(grid)): for q in range(len(grid)): if not (i == p and j == q): s.add(Not(And(grid[i][j], grid[p][q]))) #Motion primitives for t in range(HOPS): for x in range(GRID_SZ): for y in range(GRID_SZ): temp = Or(X[t][x][y]) if (x + 1 < GRID_SZ): temp = Or(temp, X[t][x + 1][y]) if (y + 1 < GRID_SZ): temp = Or(temp, X[t][x][y + 1]) if (x - 1 >= 0): temp = Or(temp, X[t][x - 1][y]) if (y - 1 >= 0): temp = Or(temp, X[t][x][y - 1]) s.add(simplify(Implies(X[t + 1][x][y], temp))) # Cost constraints for t in range(HOPS): for x in range(GRID_SZ): for y in range(GRID_SZ): s.add_soft(Not(X[t][x][y]), distance(x, y, GRID_SZ - 1, GRID_SZ - 1)) hop = 0 if s.check() == sat: m = s.model() else: print("No.of hops too low...") exit(1) obs1 = Obstacle(0, 3, GRID_SZ) robot_plan = [] obs_plan = [] # for a in s.assertions(): # print(a) while (hop < HOPS): robot_pos = (0, 0) if hop == 0 else get_robot_pos(m, hop) obs_pos = obs1.next_move() s.add(X[hop][robot_pos[0]][robot_pos[1]]) # print("hop is ", hop) # print("robot at ", robot_pos) # print("obs at ", obs_pos) if robot_pos == obs_pos: print("COLLISION!!!") print(robot_plan) print(obs_plan) exit() robot_plan.append(robot_pos) obs_plan.append(obs_pos) #next position of the robot next_robot_pos = get_robot_pos(m, hop + 1) s.push() # print("intersection points") # print(intersection_points(robot_pos, obs_pos)) # count = 0 next_overlap = next_intersection_points(next_robot_pos, obs_pos) for (x, y) in next_overlap: # consider only the intersection with the next step in the plan s.add(Not(X[hop + 1][x][y])) if len(next_overlap) > 0: # we need to find a new path if (s.check() == unsat): print("stay there") else: m = s.model() # print("Plan for hop = " + str(hop+1)) # print(get_plan(m)) hop += 1 else: # we don't need to worry about the path hop += 1 s.pop() robot_pos = get_robot_pos(m, hop) obs_pos = obs1.next_move() # print("hop is ", hop) # print("robot at ", robot_pos) # print("obs at ", obs_pos) robot_plan.append(robot_pos) obs_plan.append(obs_pos) if path_valid(robot_plan, obs_plan): print("PATH IS VALID!!!") else: print("PATH IS INVALID!!!") print("ROBOT MOVEMENT:") print(robot_plan) print("OBSTACLE MOVEMENT:") print(obs_plan)
class Oracle(ABC): """ An oracle is a dict from state_ids to values (not neccessarily probabilities since o.w. the eq system does not always have a solution). This is an abstract base class. Concrete sub-classes must overwrite `initialize`. Attributes: state_graph (StateGraph): the associated state graph default_value (Fraction): The default value is the oracle value returned if the given state_id is not a key of the oracle statistics (Statistics): access to the global statistics settings (Settings): all settings solver (Solver): a solver for the equation system oracle_states (Set[StateId]): states in this oracle oracle (Dict[StateId, z3.ExprRef]): the oracle's internal value dict """ def __init__(self, state_graph: StateGraph, default_value: Fraction, statistics: Statistics, settings: Settings, model_type: PrismModelType): self.state_graph = state_graph self.statistics = statistics self.settings = settings self.model_type = model_type if default_value < 0: raise ValueError("Oracle values must be greater or equal to 0") self.default_value = RealVal(default_value) self.solver = Solver() self.solver_mdp = Optimize() # The way we refine the Oracle depends on the model type if model_type == PrismModelType.DTMC: self.refine_oracle = self.refine_oracle_mc elif model_type == PrismModelType.MDP: self.refine_oracle = self.refine_oracle_mdp else: raise Exception("Oracle: Unsupported model type") self.oracle_states: Set[StateId] = set() self.oracle: Dict[StateId, z3.ExprRef] = dict() # self.save_oracle_on_disk() def _ensure_value_in_oracle(self, state_id: StateId): """ Used to override standard behaviour. Takes a state id, ensures that self.oracle contains this value. :param state_id: :return: """ pass def get_oracle_value(self, state_id: StateId) -> z3.ExprRef: if state_id not in self.oracle: self._ensure_value_in_oracle(state_id) return self.oracle.get(state_id, self.default_value) def refine_oracle_mc(self, visited_states: Set[StateId]) -> Set[StateId]: self.statistics.inc_refine_oracle_counter() # First ensure progress if visited_states <= self.oracle_states: # Ensure progress by adding all non-target successors of states in oracle_states to the set self.oracle_states = self.oracle_states.union({ succ_id for state_id in self.oracle_states for succ_id, prob in self.state_graph.get_filtered_successors(state_id) if succ_id != -1 }) else: self.oracle_states = self.oracle_states.union(visited_states) # TODO: A lot of optimization potential self.solver.push() # We need a variable for every oracle state variables = { state_id: Real("x_%s" % state_id) for state_id in self.oracle_states } # Set up EQ - System for state_id in self.oracle_states: self.solver.add(variables[state_id] == Sum([ RealVal(1) * prob if succ_id == -1 else # Case succ_id target state ( variables[succ_id] * prob if succ_id in self.oracle_states else # Case succ_id oracle state self.get_oracle_value(succ_id) * prob) # Case sycc_id no target and no oracle state for succ_id, prob in self.state_graph.get_filtered_successors( state_id) ])) self.solver.add(variables[state_id] >= RealVal(0)) #print(self.solver.assertions()) if self.solver.check() == sat: m = self.solver.model() # update oracle for state_id in self.oracle_states: self.oracle[state_id] = m[variables[state_id]] logger.info("Refined oracle.") #logger.info(self.oracle) self.solver.pop() return self.oracle_states else: # The oracle solver is unsat. In this case, we solve the LP. self.solver.pop() self.statistics.refine_oracle_counter = self.statistics.refine_oracle_counter - 1 return self.refine_oracle_mdp(visited_states) def refine_oracle_mdp(self, visited_states: Set[StateId]) -> Set[StateId]: self.statistics.inc_refine_oracle_counter() # First ensure progress if visited_states <= self.oracle_states: # Ensure progress by adding all non-target successors of states in oracle_states to the set (for every action) self.oracle_states = self.oracle_states.union({ succ[0] for state_id in self.oracle_states for choice in self.state_graph.get_successors_filtered(state_id).choices for succ in choice.distribution if succ[0] != -1 }) else: self.oracle_states = self.oracle_states.union(visited_states) # TODO: A lot of optimization potential self.solver_mdp.push() # We need a variable for every oracle state variables = { state_id: Real("x_%s" % state_id) for state_id in self.oracle_states } # Set up EQ - System for state_id in self.oracle_states: for choice in self.state_graph.get_successors_filtered( state_id).choices: self.solver_mdp.add(variables[state_id] >= Sum([ RealVal(1) * prob if succ_id == -1 else # Case succ_id target state ( variables[succ_id] * prob if succ_id in self.oracle_states else # Case succ_id oracle state self.get_oracle_value(succ_id) * prob) # Case sycc_id no target and no oracle state for succ_id, prob in choice.distribution ])) self.solver_mdp.add(variables[state_id] >= RealVal(0)) # Minimize value for initial state self.solver_mdp.minimize( variables[self.state_graph.get_initial_state_id()]) if self.solver_mdp.check() == sat: m = self.solver_mdp.model() # update oracle for state_id in self.oracle_states: self.oracle[state_id] = m[variables[state_id]] logger.info("Refined oracle.") # logger.info(self.oracle) self.solver_mdp.pop() return self.oracle_states else: logger.error("Oracle solver unsat") raise RuntimeError("Oracle solver inconsistent.") @abstractmethod def initialize(self): """ Stub to be overwritten by concrete oracles. :return: """ pass def save_oracle_on_disk(self): """ Save this oracle to disk using `save_oracle_dict` from `pric3.oracles.file_oracle`. """ from pric3.oracles.file_oracle import save_oracle_dict save_oracle_dict(self.state_graph, self.oracle) def _get_prism_program(self): return self.state_graph.input_program.prism_program
class StateProbabilityGenerator: def __init__(self, state_graph, statistics, settings, model_type): self.statistics = statistics self.state_graph = state_graph self.model_type = model_type logger.debug("Initialize oracle...") self._initialize_oracle(settings) logger.debug("Initialize obligation cache...") self._obligation_cache = ObligationCache() logger.debug("Initialize optimization solver...") # Initialize solver for optimization queries self.opt_solver = Optimize() self._realval_zero = RealVal(0) self._realval_one = RealVal(1) self.obligation_queue_class = settings.get_obligation_queue_class() def _initialize_oracle(self, settings): self.statistics.start_initialize_oracle_timer() args = [self.state_graph,settings.default_oracle_value,self.statistics,settings, self.model_type] if settings.oracle_type == "simulation": self.oracle = SimulationOracle(*args) elif settings.oracle_type == "perfect": self.oracle = ExactOracle(*args) elif settings.oracle_type == "modelchecking": self.oracle = ModelCheckingOracle(*args) elif settings.oracle_type == "solveeqspartly_exact" or settings.oracle_type == "solveeqspartly_inexact": self.oracle = SolveEQSPartlyOracle(*args) elif settings.oracle_type == "file": self.oracle = FileOracle(*args) else: raise RuntimeError("Unclear which oracle to use.") self.oracle.initialize() self.statistics.stop_initialize_oracle_timer() def refine_oracle(self, visited_states): res = self.oracle.refine_oracle(visited_states) self.reset_cache() return res def reset_cache(self): logger.debug("Reset obligation cache ...") self._obligation_cache.reset_cache() def finalize_statistics(self): self.statistics.set_number_oracle_states(len(self.oracle.oracle_states)) def run(self, state_id, chosen_command, delta, states_with_fixed_probabilities = set()): """ :param state_id: :param delta: :return: (1) True iff it is possible to find probabilities for the successors of the given state_id and delta. (2) If True, then it returns a dict form succ_ids to probabilities. This dict does not contain goal states. """ # TODO consider changing to None if not possible, and dict otherwise. self.statistics.inc_get_probability_counter() self.statistics.start_get_probability_timer() # First check whether we have cached the corresponding obligation res = self._obligation_cache.get_cached(state_id, chosen_command, delta) if res != False: self.statistics.stop_get_probability_timer() return (True, res) # If not, we have to ask the SMT-Solver succ_dist = self.state_graph.get_successors_filtered(state_id).by_command_index(chosen_command) succ_dist_without_target_states = [(state_id, prob) for (state_id, prob) in succ_dist if state_id != -1] # Check if there is at least one non-target state. Otherwise, repairing is not possible (smt solver would return unsat if we continued, so checking this is an optimization). if len(succ_dist_without_target_states) == 0: self.statistics.stop_get_probability_timer() return (False, None) self.opt_solver.push() vars = {} # We need a variable for each successor for (succ_id, prob) in succ_dist: if succ_id != -1: vars[succ_id] = Real("x_%s" % succ_id) # all results must of be probabilities self.opt_solver.add(vars[succ_id] >= self._realval_zero) self.opt_solver.add(vars[succ_id] <= self._realval_one) # \Phi(F)[s] = delta constraint # TODO: Type of porb is pycarl.gmp.gmp.Rational. Z3 magically deals with this self.opt_solver.add( Sum([ (vars[succ_id] if succ_id != -1 else RealVal(1)) * prob # Note: Keep in mind that you need to check whether succ is a target state for (succ_id, prob) in succ_dist ]) == delta) for (succ_id, prob) in succ_dist: if succ_id in states_with_fixed_probabilities: self.opt_solver.add(vars[succ_id] == self.obligation_queue_class.smallest_probability_for_state[succ_id]) # If we have more than one non-target successor, we have to optimize if len(succ_dist_without_target_states) > 1: # first check whether all oracle values are 0 (note that we do not have to do this if there is only one succ without target) if len(succ_dist_without_target_states) > 1 and sum([self.oracle.get_oracle_value(state_id).as_fraction() for state_id, prob in succ_dist_without_target_states]) == 0: # In this case, we require that the probability mass is distributed equally for i in range(0, len(succ_dist_without_target_states) - 1): self.opt_solver.add( vars[succ_dist_without_target_states[i][0]] == vars[succ_dist_without_target_states[i + 1][0]]) else: # First Try to solve the eq system # TODO: Do not use opt_solver for this if self._get_probabilities_by_solving_eq_system(succ_dist_without_target_states, vars): self.statistics.inc_solved_eq_system_instead_of_optimization_counter() m = self.opt_solver.model() result = { succ_id: m[vars[succ_id]] for (succ_id, prob) in succ_dist_without_target_states } # Because get_probabilities_by_solving_eq_system pushes # TODO: This is ugly # TODO: Compare solve-eq-system-time with optimization-problem-time self.opt_solver.pop() self.opt_solver.pop() self._obligation_cache.cache(state_id, chosen_command, delta, result) self.statistics.stop_get_probability_timer() return (True, result) else: self.statistics.inc_had_to_solve_optimization_problem_counter() # for each non-target-succ, we need n opt-var opt_vars = {} # For every non-target successor, we need an optimization variable for (succ_id, prob) in succ_dist_without_target_states: opt_vars[succ_id] = Real("opt_var_%s" % succ_id) # Now assert that opt_var_i = |var_i \ (var_1 + ... + var_n) - oracle(s_i) \ ( oracle(s_1) + ... + oracle(s_n ) | # for every opt_var_i for (succ_id, prob) in succ_dist_without_target_states: # opt_var is the absolute value of the ratio self.opt_solver.add( If(((vars[succ_id] * Sum([ self.oracle.get_oracle_value(succ_id_2) for (succ_id_2, prob) in succ_dist_without_target_states ])) - ((self.oracle.get_oracle_value(succ_id) * Sum([ vars[succ_id_2] for (succ_id_2, prob) in succ_dist_without_target_states ])))) < 0, opt_vars[succ_id] == (((self.oracle.get_oracle_value(succ_id) * Sum([ vars[succ_id_2] for (succ_id_2, prob) in succ_dist_without_target_states ]))) - (vars[succ_id] * Sum([ self.oracle.get_oracle_value(succ_id_2) for (succ_id_2, prob) in succ_dist_without_target_states ]))), opt_vars[succ_id] == ((vars[succ_id] * Sum([ self.oracle.get_oracle_value(succ_id_2) for (succ_id_2, prob) in succ_dist_without_target_states ])) - ((self.oracle.get_oracle_value(succ_id) * Sum([ vars[succ_id_2] for (succ_id_2, prob) in succ_dist_without_target_states ])))))) # minimize sum of opt-vars opt = self.opt_solver.minimize( Sum([ opt_vars[succ_id] for (succ_id, prob) in succ_dist_without_target_states ])) if self.opt_solver.check() == sat: # We found probabilities or the successors m = self.opt_solver.model() result = { succ_id: m[vars[succ_id]] for (succ_id, prob) in succ_dist_without_target_states } self.opt_solver.pop() self._obligation_cache.cache(state_id, chosen_command, delta, result) self.statistics.stop_get_probability_timer() return (True, result) else: # There are no such probabilities self.opt_solver.pop() self.statistics.stop_get_probability_timer() return (False, None) def _get_probabilities_by_solving_eq_system(self, succ_dist_without_target_states, vars): self.opt_solver.push() #TODO is this correct? lhs_sum = Sum([ self.oracle.get_oracle_value(succ_id_2) for (succ_id_2, _) in succ_dist_without_target_states ]) rhs_sum = Sum([ vars[succ_id_2] for (succ_id_2, _) in succ_dist_without_target_states ]) def _multiply(left,right): args = (Ast * 2)() args[0] = left.as_ast() args[1] = right.as_ast() return ArithRef(Z3_mk_mul(left.ctx.ref(), 2, args), left.ctx) for (succ_id, prob) in succ_dist_without_target_states: # opt_var is the absolute value of the ratio lhs = _multiply(vars[succ_id], lhs_sum) rhs = _multiply(self.oracle.get_oracle_value(succ_id), rhs_sum) equality = lhs == rhs self.opt_solver.add(equality) if self.opt_solver.check() == sat: return True else: self.opt_solver.pop() return False