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)
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])
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)
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()