def test_load_buffer_2(self) -> None: pb = ps.SchedulingProblem("LoadBuffer2") task_1 = ps.FixedDurationTask("task1", duration=3) task_2 = ps.FixedDurationTask("task2", duration=3) task_3 = ps.FixedDurationTask("task3", duration=3) buffer = ps.NonConcurrentBuffer("Buffer1", initial_state=10) pb.add_constraint(ps.TaskStartAt(task_1, 5)) pb.add_constraint(ps.TaskStartAt(task_2, 10)) pb.add_constraint(ps.TaskStartAt(task_3, 15)) c1 = ps.TaskLoadBuffer(task_1, buffer, quantity=3) pb.add_constraint(c1) c2 = ps.TaskLoadBuffer(task_2, buffer, quantity=2) pb.add_constraint(c2) c3 = ps.TaskLoadBuffer(task_3, buffer, quantity=1) pb.add_constraint(c3) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) self.assertEqual(solution.buffers[buffer.name].state, [10, 13, 15, 16]) self.assertEqual(solution.buffers[buffer.name].state_change_times, [8, 13, 18])
def test_operator_xor_2(self) -> None: new_problem_or_clear() t_1 = ps.FixedDurationTask("t1", duration=2) with self.assertRaises(TypeError): ps.xor_( [ps.TaskStartAt(t_1, 1), ps.TaskStartAt(t_1, 2), ps.TaskStartAt(t_1, 3)] )
def test_nested_boolean_operators(self) -> None: new_problem_or_clear() t_1 = ps.VariableDurationTask("t1") or_constraint_1 = ps.or_([ps.TaskStartAt(t_1, 1), ps.TaskStartAt(t_1, 2)]) or_constraint_2 = ps.or_([ps.TaskStartAt(t_1, 4), ps.TaskStartAt(t_1, 5)]) and_constraint = ps.and_([or_constraint_1, or_constraint_2]) self.assertIsInstance(and_constraint, ps.BoolRef)
def test_add_constraint(self) -> None: pb = ps.SchedulingProblem("AddConstraint") t_1 = ps.FixedDurationTask("t1", duration=2) or_constraint = ps.or_([ps.TaskStartAt(t_1, 1), ps.TaskStartAt(t_1, 2)]) not_constraint = ps.not_(ps.TaskEndAt(t_1, 5)) self.assertIsInstance(or_constraint, ps.BoolRef) self.assertIsInstance(not_constraint, ps.BoolRef) pb.add_constraint(or_constraint) pb.add_constraint(not_constraint)
def test_if_then_else(self) -> None: new_problem_or_clear() t_1 = ps.FixedDurationTask("t1", 2) t_2 = ps.FixedDurationTask("t2", 2) ite_constraint = ps.if_then_else( t_1.start == 1, # condition [ps.TaskStartAt(t_2, 3)], # then [ps.TaskStartAt(t_2, 6)], ) # else self.assertIsInstance(ite_constraint, ps.BoolRef)
def test_optional_constraint_start_at_1(self) -> None: pb = ps.SchedulingProblem("OptionalTaskStartAt1", horizon=6) task_1 = ps.FixedDurationTask("task1", duration=3) # the following tasks should conflict if they are mandatory ps.TaskStartAt(task_1, 1, optional=True) ps.TaskStartAt(task_1, 2, optional=True) ps.TaskEndAt(task_1, 3, optional=True) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution)
def test_implies(self): problem = ps.SchedulingProblem("Implies", horizon=6) # only one task, the solver should schedule a start time at 0 task_1 = ps.FixedDurationTask("task1", duration=2) task_2 = ps.FixedDurationTask("task2", duration=2) ps.TaskStartAt(task_1, 1) fol_1 = ps.implies(task_1.start == 1, [ps.TaskStartAt(task_2, 4)]) problem.add_constraint(fol_1) solution = _solve_problem(problem) self.assertTrue(solution) # the only solution is to start at 2 self.assertTrue(solution.tasks[task_1.name].start == 1) self.assertTrue(solution.tasks[task_2.name].start == 4)
def test_operator_not_and(self): problem = ps.SchedulingProblem("OperatorNotAnd", horizon=4) # only one task, the solver should schedule a start time at 0 task_1 = ps.FixedDurationTask("task1", duration=2) # problem.add_task(task_1) fol_1 = ps.and_([ ps.not_(ps.TaskStartAt(task_1, 0)), ps.not_(ps.TaskStartAt(task_1, 1)) ]) problem.add_constraint(fol_1) solution = _solve_problem(problem) self.assertTrue(solution) # the only solution is to start at 2 self.assertTrue(solution.tasks[task_1.name].start == 2)
def test_operator_or_1(self) -> None: pb = ps.SchedulingProblem("OperatorOr1", horizon=8) t_1 = ps.FixedDurationTask("t1", duration=2) or_constraint = ps.or_( [ps.TaskStartAt(t_1, 3), ps.TaskStartAt(t_1, 6)]) self.assertIsInstance(or_constraint, ps.BoolRef) pb.add_constraint(or_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertTrue(solution) self.assertTrue(solution.tasks["t1"].start in [3, 6])
def test_force_apply_n_optional_constraints(self) -> None: pb = ps.SchedulingProblem("OptionalTaskStartAt1", horizon=6) task_1 = ps.FixedDurationTask("task1", duration=3) # the following tasks should conflict if they are mandatory cstr1 = ps.TaskStartAt(task_1, 1, optional=True) cstr2 = ps.TaskStartAt(task_1, 2, optional=True) cstr3 = ps.TaskEndAt(task_1, 3, optional=True) # force to apply exactly one constraint ps.ForceApplyNOptionalConstraints([cstr1, cstr2, cstr3], 1) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution)
def test_resource_tasks_distance_1(self) -> None: pb = ps.SchedulingProblem("ResourceTasksDistance1", horizon=20) task_1 = ps.FixedDurationTask("task1", duration=8) task_2 = ps.FixedDurationTask("task2", duration=4) worker_1 = ps.Worker("Worker1") task_1.add_required_resource(worker_1) task_2.add_required_resource(worker_1) c1 = ps.ResourceTasksDistance(worker_1, distance=4, mode="exact") pb.add_constraint(c1) pb.add_constraint(ps.TaskStartAt(task_1, 1)) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) t1_start = solution.tasks[task_1.name].start t2_start = solution.tasks[task_2.name].start t1_end = solution.tasks[task_1.name].end t2_end = solution.tasks[task_2.name].end self.assertEqual(t1_start, 1) self.assertEqual(t1_end, 9) self.assertEqual(t2_start, 13) self.assertEqual(t2_end, 17)
def test_operator_xor_1(self) -> None: # same as OperatorOr2 but with an exlusive or, there # is no solution pb = ps.SchedulingProblem("OperatorXor1", horizon=2) t_1 = ps.FixedDurationTask("t1", duration=2) t_2 = ps.FixedDurationTask("t2", duration=2) xor_constraint = ps.xor_( [ps.TaskStartAt(t_1, 0), ps.TaskStartAt(t_2, 0)]) self.assertIsInstance(xor_constraint, ps.BoolRef) pb.add_constraint(xor_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertFalse(solution)
def test_non_dynamic_1(self) -> None: # same test as previously # but the task_1 workers are non dynamic. # so the horizon must be 20. pb = ps.SchedulingProblem("NonDynamicTest1") task_1 = ps.FixedDurationTask("task1", duration=10, work_amount=10) task_2 = ps.FixedDurationTask("task2", duration=5) task_3 = ps.FixedDurationTask("task3", duration=5) pb.add_constraint(ps.TaskStartAt(task_3, 0)) pb.add_constraint(ps.TaskEndAt(task_2, 10)) worker_1 = ps.Worker("Worker1", productivity=1) worker_2 = ps.Worker("Worker2", productivity=1) task_1.add_required_resources([worker_1, worker_2]) # dynamic False by default task_2.add_required_resource(worker_1) task_3.add_required_resource(worker_2) pb.add_objective_makespan() solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertEqual(solution.horizon, 20)
def test_resource_tasks_distance_3(self) -> None: pb = ps.SchedulingProblem("ResourceTasksDistanceContiguous3") task_1 = ps.FixedDurationTask("task1", duration=8) task_2 = ps.FixedDurationTask("task2", duration=4) task_3 = ps.FixedDurationTask("task3", duration=5) worker_1 = ps.Worker("Worker1") task_1.add_required_resource(worker_1) task_2.add_required_resource(worker_1) task_3.add_required_resource(worker_1) # a constraint to tell tasks are contiguous ps.ResourceTasksDistance(worker_1, distance=0, mode="exact") ps.TaskStartAt(task_1, 2) pb.add_objective_makespan() solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) t1_start = solution.tasks[task_1.name].start t2_start = solution.tasks[task_2.name].start t3_start = solution.tasks[task_3.name].start t1_end = solution.tasks[task_1.name].end self.assertEqual(t1_start, 2) self.assertEqual(t1_end, 10) self.assertTrue(t3_start in [10, 14]) self.assertTrue(t2_start in [10, 15])
def test_optional_cumulative(self): """Same as above, but with an optional taskand an horizon of 2. t2 should not be scheduled.""" pb_bs = ps.SchedulingProblem("OptionalCumulative", 2) # tasks t1 = ps.FixedDurationTask("T1", duration=2) t2 = ps.FixedDurationTask("T2", duration=2, optional=True) t3 = ps.FixedDurationTask("T3", duration=2) # workers r1 = ps.CumulativeWorker("Machine1", size=2) # resource assignment t1.add_required_resource(r1) t2.add_required_resource(r1) # constraints pb_bs.add_constraint(ps.TaskStartAt(t2, 1)) # plot solution solver = ps.SchedulingSolver(pb_bs) solution = solver.solve() self.assertTrue(solution) self.assertTrue(solution.tasks[t1.name].scheduled) self.assertTrue(solution.tasks[t3.name].scheduled) self.assertFalse(solution.tasks[t2.name].scheduled)
def test_load_unload_feed_buffers_1(self) -> None: # one task that consumes and feed two different buffers pb = ps.SchedulingProblem("LoadUnloadBuffer1") task_1 = ps.FixedDurationTask("task1", duration=3) buffer_1 = ps.NonConcurrentBuffer("Buffer1", initial_state=10) buffer_2 = ps.NonConcurrentBuffer("Buffer2", initial_state=0) pb.add_constraint(ps.TaskStartAt(task_1, 5)) c1 = ps.TaskUnloadBuffer(task_1, buffer_1, quantity=3) pb.add_constraint(c1) c2 = ps.TaskLoadBuffer(task_1, buffer_2, quantity=2) pb.add_constraint(c2) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) self.assertEqual(solution.buffers[buffer_1.name].state, [10, 7]) self.assertEqual(solution.buffers[buffer_1.name].state_change_times, [5]) self.assertEqual(solution.buffers[buffer_2.name].state, [0, 2]) self.assertEqual(solution.buffers[buffer_2.name].state_change_times, [8]) # plot buffers solution.render_gantt_matplotlib(show_plot=False)
def test_resource_tasks_distance_single_time_period_2(self) -> None: """The same as above, except that we force the tasks to be scheduled in the time period so that the distance applies""" pb = ps.SchedulingProblem("ResourceTasksDistanceSingleTimePeriod2") task_1 = ps.FixedDurationTask("task1", duration=1) task_2 = ps.FixedDurationTask("task2", duration=1) worker_1 = ps.Worker("Worker1") task_1.add_required_resource(worker_1) task_2.add_required_resource(worker_1) ps.ResourceTasksDistance(worker_1, distance=4, mode="exact", list_of_time_intervals=[[10, 18]]) # force task 1 to start at 10 (in the time period intervak) ps.TaskStartAt(task_1, 10) # task_2 must be scheduled after task_1 ps.TaskPrecedence(task_1, task_2) # add a makespan objective, to be sure, to schedule task_2 in the time interal pb.add_objective_makespan() # as a consequence, task2 should be scheduled 4 periods after and start at 15 solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) self.assertEqual(solution.tasks[task_2.name].start, 15) self.assertEqual(solution.tasks[task_2.name].end, 16)
def test_resource_tasks_distance_2(self) -> None: pb = ps.SchedulingProblem("ResourceTasksDistance2") task_1 = ps.FixedDurationTask("task1", duration=8) task_2 = ps.FixedDurationTask("task2", duration=4) worker_1 = ps.Worker("Worker1") task_1.add_required_resource(worker_1) task_2.add_required_resource(worker_1) ps.ResourceTasksDistance(worker_1, distance=4, mode="max") ps.TaskStartAt(task_1, 1) pb.add_objective_makespan() solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) t1_start = solution.tasks[task_1.name].start t2_start = solution.tasks[task_2.name].start t1_end = solution.tasks[task_1.name].end t2_end = solution.tasks[task_2.name].end self.assertEqual(t1_start, 1) self.assertEqual(t1_end, 9) self.assertEqual(t2_start, 9) self.assertEqual(t2_end, 13)
def test_resource_tasks_distance_4(self) -> None: """Adding one or more non scheduled optional tasks should not change anything""" pb = ps.SchedulingProblem("ResourceTasksDistance4OptionalTasks", horizon=20) task_1 = ps.FixedDurationTask("task1", duration=8) task_2 = ps.FixedDurationTask("task2", duration=4) task_3 = ps.FixedDurationTask("task3", duration=3, optional=True) task_4 = ps.FixedDurationTask("task4", duration=2, optional=True) task_5 = ps.FixedDurationTask("task5", duration=1, optional=True) worker_1 = ps.Worker("Worker1") task_1.add_required_resource(worker_1) task_2.add_required_resource(worker_1) ps.ResourceTasksDistance(worker_1, distance=4, mode="exact") ps.TaskStartAt(task_1, 1) solver = ps.SchedulingSolver(pb) # for optional tasks to not be scheduled solver.append_z3_assertion(task_3.scheduled == False) solver.append_z3_assertion(task_4.scheduled == False) solver.append_z3_assertion(task_5.scheduled == False) solution = solver.solve() self.assertTrue(solution) t1_start = solution.tasks[task_1.name].start t2_start = solution.tasks[task_2.name].start t1_end = solution.tasks[task_1.name].end t2_end = solution.tasks[task_2.name].end self.assertEqual(t1_start, 1) self.assertEqual(t1_end, 9) self.assertEqual(t2_start, 13) self.assertEqual(t2_end, 17)
def test_resource_tasks_distance_double_time_period_2(self) -> None: """Same as above, but force the tasks to be scheduled within the time intervals""" pb = ps.SchedulingProblem("ResourceTasksDistanceMultiTimePeriod2") tasks = [ ps.FixedDurationTask("task%i" % i, duration=1) for i in range(4) ] worker_1 = ps.Worker("Worker1") for t in tasks: t.add_required_resource(worker_1) c1 = ps.ResourceTasksDistance(worker_1, distance=4, mode="exact", time_periods=[[10, 20], [30, 40]]) pb.add_constraint(c1) # add a makespan objective, all tasks should be scheduled from 0 to 4 pb.add_constraint(ps.TaskStartAt(tasks[0], 10)) pb.add_constraint(ps.TaskStartAfterLax(tasks[1], 10)) pb.add_constraint(ps.TaskEndBeforeLax(tasks[1], 20)) pb.add_constraint(ps.TaskStartAt(tasks[2], 30)) pb.add_constraint(ps.TaskStartAfterLax(tasks[3], 30)) pb.add_constraint(ps.TaskEndBeforeLax(tasks[3], 40)) # as a consequence, task2 should be scheduled 4 periods after and start at 15 solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) self.assertEqual(solution.horizon, 36) t0_start = solution.tasks[tasks[0].name].start t1_start = solution.tasks[tasks[1].name].start t2_start = solution.tasks[tasks[2].name].start t3_start = solution.tasks[tasks[3].name].start t0_end = solution.tasks[tasks[0].name].end t1_end = solution.tasks[tasks[1].name].end t2_end = solution.tasks[tasks[2].name].end t3_end = solution.tasks[tasks[3].name].end self.assertEqual(t0_start, 10) self.assertEqual(t0_end, 11) self.assertEqual(t1_start, 15) self.assertEqual(t1_end, 16) self.assertEqual(t2_start, 30) self.assertEqual(t2_end, 31) self.assertEqual(t3_start, 35) self.assertEqual(t3_end, 36)
def test_if_then_else(self): problem = ps.SchedulingProblem("IfThenElse", horizon=6) # only one task, the solver should schedule a start time at 0 task_1 = ps.FixedDurationTask("task1", duration=2) task_2 = ps.FixedDurationTask("task2", duration=2) ps.TaskStartAt(task_1, 1) fol_1 = ps.if_then_else( task_1.start == 0, # this condition is False [ps.TaskStartAt(task_2, 4)], # assertion not set [ps.TaskStartAt(task_2, 2)], ) problem.add_constraint(fol_1) solution = _solve_problem(problem) self.assertTrue(solution) # the only solution is to start at 2 self.assertTrue(solution.tasks[task_1.name].start == 1) self.assertTrue(solution.tasks[task_2.name].start == 2)
def test_nested_boolean_operators_1(self) -> None: pb = ps.SchedulingProblem("NestedBooleanOperators1", horizon=37) t_1 = ps.FixedDurationTask("t1", duration=2) t_2 = ps.FixedDurationTask("t2", duration=2) or_constraint_1 = ps.or_( [ps.TaskStartAt(t_1, 10), ps.TaskStartAt(t_1, 12)]) or_constraint_2 = ps.or_( [ps.TaskStartAt(t_2, 14), ps.TaskStartAt(t_2, 15)]) and_constraint = ps.and_([or_constraint_1, or_constraint_2]) pb.add_constraint(and_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertTrue(solution) self.assertTrue(solution.tasks["t1"].start in [10, 12]) self.assertTrue(solution.tasks["t2"].start in [14, 15])
def test_task_duration_depend_on_start(self) -> None: pb = ps.SchedulingProblem("TaskDurationDependsOnStart", horizon=30) task_1 = ps.VariableDurationTask("Task1") task_2 = ps.VariableDurationTask("Task2") ps.TaskStartAt(task_1, 5) pb.add_constraint(task_1.duration == task_1.start * 3) ps.TaskStartAt(task_2, 11) pb.add_constraint( ps.if_then_else(task_2.start < 10, [task_2.duration == 3], [task_2.duration == 1])) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) self.assertEqual(solution.tasks["Task1"].duration, 15) self.assertEqual(solution.tasks["Task2"].duration, 1)
def test_if_then_else(self) -> None: pb = ps.SchedulingProblem("IfThenElse1", horizon=29) t_1 = ps.FixedDurationTask("t1", 3) t_2 = ps.FixedDurationTask("t2", 3) ite_constraint = ps.if_then_else( t_1.start == 1, # condition [ps.TaskStartAt(t_2, 3)], # then [ps.TaskStartAt(t_2, 6)], # else ) self.assertIsInstance(ite_constraint, ps.BoolRef) # force task t_1 to start at 2 ps.TaskStartAt(t_1, 2) pb.add_constraint(ite_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertTrue(solution) self.assertEqual(solution.tasks["t1"].start, 2) self.assertEqual(solution.tasks["t2"].start, 6)
def test_operator_not_1(self) -> None: pb = ps.SchedulingProblem("OperatorNot1", horizon=3) t_1 = ps.FixedDurationTask("t1", duration=2) not_constraint = ps.not_(ps.TaskStartAt(t_1, 0)) self.assertIsInstance(not_constraint, ps.BoolRef) pb.add_constraint(not_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertTrue(solution) self.assertEqual(solution.tasks["t1"].start, 1)
def test_operator_xor_2(self) -> None: # same as OperatorXOr1 but with a larger horizon # then there should be a solution pb = ps.SchedulingProblem("OperatorXor2", horizon=3) t_1 = ps.FixedDurationTask("t1", duration=2) t_2 = ps.FixedDurationTask("t2", duration=2) xor_constraint = ps.xor_( [ps.TaskStartAt(t_1, 0), ps.TaskStartAt(t_2, 0)]) self.assertIsInstance(xor_constraint, ps.BoolRef) pb.add_constraint(xor_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertTrue(solution) self.assertTrue( solution.tasks["t1"].start != solution.tasks["t2"].start) self.assertTrue(solution.tasks["t1"].start == 0 or solution.tasks["t2"].start == 0)
def test_tasks_start_sync(self) -> None: pb = ps.SchedulingProblem("TasksStartSync") t_1 = ps.FixedDurationTask("t1", duration=2) t_2 = ps.FixedDurationTask("t2", duration=3) ps.TaskStartAt(t_1, 7) ps.TasksStartSynced(t_1, t_2) solver = ps.SchedulingSolver(pb) solution = solver.solve() self.assertTrue(solution) self.assertEqual(solution.tasks[t_1.name].start, solution.tasks[t_2.name].start)
def test_unsat_1(self): problem = ps.SchedulingProblem("Unsat1") task = ps.FixedDurationTask("task", duration=7) # add two constraints to set start and end # impossible to satisfy both ps.TaskStartAt(task, 1) ps.TaskEndAt(task, 4) self.assertFalse(_solve_problem(problem))
def test_implies(self) -> None: pb = ps.SchedulingProblem("Implies1", horizon=37) t_1 = ps.FixedDurationTask("t1", 2) t_2 = ps.FixedDurationTask("t2", 4) t_3 = ps.FixedDurationTask("t3", 7) # force task t_1 to start at 1 ps.TaskStartAt(t_1, 1) implies_constraint = ps.implies( t_1.start == 1, [ps.TaskStartAt(t_2, 3), ps.TaskStartAt(t_3, 17)]) self.assertIsInstance(implies_constraint, ps.BoolRef) pb.add_constraint(implies_constraint) solution = ps.SchedulingSolver(pb).solve() self.assertTrue(solution) self.assertEqual(solution.tasks["t1"].start, 1) self.assertEqual(solution.tasks["t2"].start, 3) self.assertEqual(solution.tasks["t3"].start, 17)