def __init__(
        self,
        worker,
        distance: int,
        time_periods: Optional[list] = None,
        optional: Optional[bool] = False,
        mode: Optional[str] = "exact",
    ):
        if mode not in {"min", "max", "exact"}:
            raise Exception("Mode should be min, max or exact")

        super().__init__(optional)

        starts = []
        ends = []
        for start_var, end_var in worker.busy_intervals.values():
            starts.append(start_var)
            ends.append(end_var)
        # sort both lists
        sorted_starts, c1 = sort_no_duplicates(starts)
        sorted_ends, c2 = sort_no_duplicates(ends)
        for c in c1 + c2:
            self.set_assertions(c)
        # from now, starts and ends are sorted in asc order
        # the space between two consecutive tasks is the sorted_start[i+1]-sorted_end[i]
        # we just have to constraint this variable
        for i in range(1, len(sorted_starts)):
            if mode == "min":
                asst = sorted_starts[i] - sorted_ends[i - 1] >= distance
            elif mode == "max":
                asst = sorted_starts[i] - sorted_ends[i - 1] <= distance
            elif mode == "exact":
                asst = sorted_starts[i] - sorted_ends[i - 1] == distance
            #  another set of conditions, related to the time periods
            conditions = []
            if time_periods is not None:
                for (
                        lower_bound,
                        upper_bound,
                ) in time_periods:  # time_period should be a list also or a tuple
                    conditions.append(
                        And(
                            sorted_starts[i] >= lower_bound,
                            sorted_ends[i - 1] >= lower_bound,
                            sorted_starts[i] <= upper_bound,
                            sorted_ends[i - 1] <= upper_bound,
                        ))
            else:
                # add the constraint only if start and ends are positive integers,
                # that is to say they correspond to a scheduled optional task
                condition_only_scheduled_tasks = And(sorted_ends[i - 1] >= 0,
                                                     sorted_starts[i] >= 0)
                conditions = [condition_only_scheduled_tasks]
            # finally create the constraint
            new_cstr = Implies(Or(conditions), asst)
            self.set_assertions(new_cstr)
Exemplo n.º 2
0
 def test_sort_no_duplicates(self):
     lst_to_sort = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2]
     sorted_variables, assertions = sort_no_duplicates(lst_to_sort)
     s = Solver()
     s.add(assertions)
     result = s.check()
     solution = s.model()
     sorted_integers = [solution[v].as_long() for v in sorted_variables]
     self.assertEqual(result, sat)
     self.assertEqual(sorted_integers,
                      [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
Exemplo n.º 3
0
    def __init__(self, list_of_tasks, optional: Optional[bool] = False) -> None:
        super().__init__(optional)

        starts = [t.start for t in list_of_tasks]
        ends = [t.end for t in list_of_tasks]
        # sort both lists
        sorted_starts, c1 = sort_no_duplicates(starts)
        sorted_ends, c2 = sort_no_duplicates(ends)
        for c in c1 + c2:
            self.set_z3_assertions(c)
        # from now, starts and ends are sorted in asc order
        # the space between two consecutive tasks is the sorted_start[i+1]-sorted_end[i]
        # we just have to constraint this variable
        for i in range(1, len(sorted_starts)):
            asst = sorted_starts[i] == sorted_ends[i - 1]
            #  another set of conditions, related to the time periods
            condition_only_scheduled_tasks = And(
                sorted_ends[i - 1] >= 0, sorted_starts[i] >= 0
            )
            # finally create the constraint
            new_cstr = Implies(Or(condition_only_scheduled_tasks), asst)
            self.set_z3_assertions(new_cstr)
    def __init__(
        self,
        resource,
        distance: int,
        list_of_time_intervals: Optional[list] = None,
        optional: Optional[bool] = False,
        mode: Optional[str] = "exact",
    ):
        if mode not in {"min", "max", "exact"}:
            raise Exception("Mode should be min, max or exact")

        super().__init__(optional)

        self.list_of_time_intervals = list_of_time_intervals
        self.resource = resource
        self.distance = distance
        self.mode = mode

        starts = []
        ends = []
        for start_var, end_var in resource.busy_intervals.values():
            starts.append(start_var)
            ends.append(end_var)

        # check that the resource is assigned to at least two tasks
        if len(starts) < 2:
            raise AssertionError(
                "The resource has to be assigned to at least 2 tasks. ResourceTasksDistance constraint meaningless."
            )

        # sort both lists
        sorted_starts, c1 = sort_no_duplicates(starts)
        sorted_ends, c2 = sort_no_duplicates(ends)
        for c in c1 + c2:
            self.set_z3_assertions(c)
        # from now, starts and ends are sorted in asc order
        # the space between two consecutive tasks is the sorted_start[i+1]-sorted_end[i]
        # we just have to constraint this variable
        for i in range(1, len(sorted_starts)):
            if mode == "min":
                asst = sorted_starts[i] - sorted_ends[i - 1] >= distance
            elif mode == "max":
                asst = sorted_starts[i] - sorted_ends[i - 1] <= distance
            elif mode == "exact":
                asst = sorted_starts[i] - sorted_ends[i - 1] == distance
            #  another set of conditions, related to the time periods
            conditions = []
            if list_of_time_intervals is not None:
                for (
                    lower_bound,
                    upper_bound,
                ) in list_of_time_intervals:
                    conditions.append(
                        And(
                            sorted_starts[i] >= lower_bound,
                            sorted_ends[i - 1] >= lower_bound,
                            sorted_starts[i] <= upper_bound,
                            sorted_ends[i - 1] <= upper_bound,
                        )
                    )
            else:
                # add the constraint only if start and ends are positive integers,
                # that is to say they correspond to a scheduled optional task
                condition_only_scheduled_tasks = And(
                    sorted_ends[i - 1] >= 0, sorted_starts[i] >= 0
                )
                conditions = [condition_only_scheduled_tasks]
            # finally create the constraint
            new_cstr = Implies(Or(conditions), asst)
            self.set_z3_assertions(new_cstr)
Exemplo n.º 5
0
    def __init__(
        self,
        problem,
        debug: Optional[bool] = False,
        max_time: Optional[int] = 10,
        parallel: Optional[bool] = False,
        random_values: Optional[bool] = False,
        logics: Optional[str] = None,
        verbosity: Optional[int] = 0,
    ):
        """Scheduling Solver

        debug: True or False, False by default
        max_time: time in seconds, 60 by default
        parallel: True to enable mutlthreading, False by default
        """
        self.problem = problem
        self.problem_context = problem.context
        self.debug = debug
        # objectives list
        self.objective = None  # the list of all objectives defined in this problem
        self.current_solution = None  # no solution until the problem is solved

        # set_option('smt.arith.auto_config_simplex', True)
        if debug:
            set_option("verbose", 2)
        else:
            set_option("verbose", verbosity)

        if random_values:
            set_option("sat.random_seed", random.randint(1, 1e3))
            set_option("smt.random_seed", random.randint(1, 1e3))
            set_option("smt.arith.random_initial_value", True)
        else:
            set_option("sat.random_seed", 0)
            set_option("smt.random_seed", 0)
            set_option("smt.arith.random_initial_value", False)

        # set timeout
        self.max_time = max_time  # in seconds
        set_option("timeout", int(self.max_time * 1000))  # in milliseconds

        # create the solver
        print("Solver type:\n===========")

        # check if the problem is an optimization problem
        self.is_not_optimization_problem = len(
            self.problem_context.objectives) == 0
        self.is_optimization_problem = len(self.problem_context.objectives) > 0
        self.is_multi_objective_optimization_problem = (len(
            self.problem_context.objectives) > 1)
        # the Optimize() solver is used only in the case of a mutli-optimization
        # problem. This enables to choose the priority method.
        # in the case of a single objective optimization, the Optimize() solver
        # apperas to be less robust than the basic Solver(). The
        # incremental solver is then used.

        # see this url for a documentation about logics
        # http://smtlib.cs.uiowa.edu/logics.shtml
        if logics is None:
            self._solver = Solver()
            print("\t-> Standard SAT/SMT solver")
        else:
            self._solver = SolverFor(logics)
            print("\t-> SMT solver using logics", logics)
        if debug:
            set_option(unsat_core=True)

        if parallel:
            set_option("parallel.enable", True)  # enable parallel computation

        # add all tasks assertions to the solver
        for task in self.problem_context.tasks:
            self.add_constraint(task.get_assertions())
            self.add_constraint(task.end <= self.problem.horizon)

        # then process tasks constraints
        for constraint in self.problem_context.constraints:
            self.add_constraint(constraint)

        # process resources requirements
        for ress in self.problem_context.resources:
            self.add_constraint(ress.get_assertions())

        # process resource intervals
        for ress in self.problem_context.resources:
            busy_intervals = ress.get_busy_intervals()
            nb_intervals = len(busy_intervals)
            for i in range(nb_intervals):
                start_task_i, end_task_i = busy_intervals[i]
                for k in range(i + 1, nb_intervals):
                    start_task_k, end_task_k = busy_intervals[k]
                    self.add_constraint(
                        Or(start_task_k >= end_task_i,
                           start_task_i >= end_task_k))

        # process indicators
        for indic in self.problem_context.indicators:
            self.add_constraint(indic.get_assertions())

        # work amounts
        # for each task, compute the total work for all required resources"""
        for task in self.problem_context.tasks:
            if task.work_amount > 0.0:
                work_total_for_all_resources = []
                for required_resource in task.required_resources:
                    # work contribution for the resource
                    interv_low, interv_up = required_resource.busy_intervals[
                        task]
                    work_contribution = required_resource.productivity * (
                        interv_up - interv_low)
                    work_total_for_all_resources.append(work_contribution)
                self.add_constraint(
                    Sum(work_total_for_all_resources) >= task.work_amount)

        # process buffers
        for buffer in self.problem_context.buffers:
            #
            # create an array that stores the mapping between start times and
            # quantities. For example, if a start T1 starts at 2 and consumes
            # 8, and T3 ends at 6 and consumes 5 then the mapping array
            # will look like : A[2]=8 and A[6]=-5
            # SO far, no way to have the same start time at different inst
            buffer_mapping = Array("Buffer_%s_mapping" % buffer.name,
                                   IntSort(), IntSort())
            for t in buffer.unloading_tasks:
                self.add_constraint(buffer_mapping == Store(
                    buffer_mapping, t.start, -buffer.unloading_tasks[t]))
            for t in buffer.loading_tasks:
                self.add_constraint(buffer_mapping == Store(
                    buffer_mapping, t.end, +buffer.loading_tasks[t]))
            # sort consume/feed times in asc order
            tasks_start_unload = [t.start for t in buffer.unloading_tasks]
            tasks_end_load = [t.end for t in buffer.loading_tasks]

            sorted_times, sort_assertions = sort_no_duplicates(
                tasks_start_unload + tasks_end_load)
            self.add_constraint(sort_assertions)
            # create as many buffer state changes as sorted_times
            buffer.state_changes_time = [
                Int("%s_sc_time_%i" % (buffer.name, k))
                for k in range(len(sorted_times))
            ]

            # add the constraints that give the buffer state change times
            for st, bfst in zip(sorted_times, buffer.state_changes_time):
                self.add_constraint(st == bfst)

            # compute the different buffer states according to state changes
            buffer.buffer_states = [
                Int("%s_state_%i" % (buffer.name, k))
                for k in range(len(buffer.state_changes_time) + 1)
            ]
            # add constraints for buffer states
            # the first buffer state is equal to the buffer initial level
            if buffer.initial_state is not None:
                self.add_constraint(
                    buffer.buffer_states[0] == buffer.initial_state)
            if buffer.final_state is not None:
                self.add_constraint(
                    buffer.buffer_states[-1] == buffer.final_state)
            if buffer.lower_bound is not None:
                for st in buffer.buffer_states:
                    self.add_constraint(st >= buffer.lower_bound)
            if buffer.upper_bound is not None:
                for st in buffer.buffer_states:
                    self.add_constraint(st <= buffer.upper_bound)
            # and, for the other, the buffer state i+1 is the buffer state i +/- the buffer change
            for i in range(len(buffer.buffer_states) - 1):
                self.add_constraint(
                    buffer.buffer_states[i + 1] == buffer.buffer_states[i] +
                    buffer_mapping[buffer.state_changes_time[i]])

        # optimization
        if self.is_optimization_problem:
            self.create_objective()