Exemple #1
0
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)
Exemple #2
0
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)
Exemple #4
0
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
Exemple #5
0
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