Example #1
0
    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])
Example #2
0
 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)]
         )
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
 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)
Example #7
0
 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)
Example #8
0
 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)
Example #9
0
    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)
Example #11
0
    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)
Example #12
0
    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)
Example #16
0
    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)
Example #20
0
    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)
Example #21
0
 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)
Example #22
0
    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])
Example #23
0
    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)
Example #24
0
    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)
Example #25
0
    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)
Example #26
0
    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)
Example #27
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)
Example #28
0
    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))
Example #29
0
    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)