Job Scheduling

๐Ÿ“… Example: Job Scheduling

Description

Let’s consider a scheduling problem, specifically the job scheduling problem. In this problem, you have a set of jobs, each with a processing time and a deadline. The goal is to schedule the jobs on a single machine to minimize the total tardiness, where tardiness is the amount of time a job is completed after its deadline. Let’s set up a simple job scheduling problem and solve it using Qaekwy.

The problem is approached by defining variables to represent the completion times of each job, constraints to ensure that the completion times are greater than or equal to the processing times, and an objective function to minimize the total tardiness.

Variables

In this Job Scheduling Problem model, integer variables are used to represent the completion times of each job. Each job corresponds to an integer variable, which represents the time when the job is completed.

Constraints

  • Completion Time Constraint: The completion time of each job must be greater than or equal to its processing time. This constraint ensures that a job cannot complete before it starts processing.

Objective

The objective of this Job Scheduling Problem is to minimize the total tardiness, which is calculated by summing the tardiness of each job. Tardiness is computed as the difference between the completion time of a job and its deadline, but only considering positive values.

The model is solved using the qaekwy optimization engine, and the solutions are retrieved from the response. For each solution, the scheduled jobs are printed along with their completion times and tardiness values. The total tardiness across all scheduled jobs is also calculated and displayed.

Code

from qaekwy.engine import DirectEngine
from qaekwy.model.constraint.relational import RelationalExpression
from qaekwy.model.specific import SpecificMinimum
from qaekwy.model.variable.branch import BranchIntegerVal
from qaekwy.model.variable.integer import IntegerVariable, IntegerExpressionVariable
from qaekwy.model.modeller import Modeller
from qaekwy.model.searcher import SearcherType


class JobSchedulingProblem(Modeller):
    def __init__(self, processing_times, deadlines):
        super().__init__()

        num_jobs = len(processing_times)

        # Completion time variables
        completion_times = [
            IntegerVariable(
                var_name=f"completionTime_{i}", domain_low=0, domain_high=20,
                branch_val=BranchIntegerVal.VAL_MIN
            )
            for i in range(num_jobs)
        ]

        # Tardiness variables
        tardiness_vars = [
            IntegerVariable(
                var_name=f"tardiness_{i}", domain_low=0, domain_high=20,
                branch_val=BranchIntegerVal.VAL_MIN
            )
            for i in range(num_jobs)
        ]

        # Enforce sequential processing on a single machine
        # Job 0 finishes at its processing time
        self.add_constraint(RelationalExpression(completion_times[0] == processing_times[0]))

        # Each next job completes after the previous one
        for i in range(1, num_jobs):
            self.add_constraint(
                RelationalExpression(
                    completion_times[i] == completion_times[i-1] + processing_times[i]
                )
            )

        # Tardiness constraints
        for i in range(num_jobs):
            self.add_constraint(
                RelationalExpression(
                    tardiness_vars[i] >= completion_times[i] - deadlines[i]
                )
            )
            self.add_constraint(RelationalExpression(tardiness_vars[i] >= 0))

        # Total tardiness
        total_tardiness = IntegerExpressionVariable(
            var_name="total_tardiness",
            expression=sum(tardiness_vars),
            branch_val=BranchIntegerVal.VAL_MIN
        )

        # Add everything
        for v in completion_times + tardiness_vars + [total_tardiness]:
            self.add_variable(v)

        # Objective: Minimize total tardiness
        self.add_objective(SpecificMinimum(total_tardiness))

        # Use Branch-and-Bound search
        self.set_searcher(SearcherType.BAB)


if __name__ == "__main__":
    # Engine
    qaekwy_engine = DirectEngine()

    # Example data
    processing_times = [2, 3, 4, 1, 5]
    deadlines = [4, 7, 8, 5, 10]

    # Model
    job_scheduling_problem = JobSchedulingProblem(processing_times, deadlines)

    # Solve
    response = qaekwy_engine.model(model=job_scheduling_problem)
    solutions = response.get_solutions()
    solution = solutions[0]  # best solution

    # Print results
    for i in range(len(processing_times)):
        completion = solution[f"completionTime_{i}"]
        tardiness = solution[f"tardiness_{i}"]
        print(f"Job {i+1}: Completion Time = {completion}, Tardiness = {tardiness}")

    print(f"Total tardiness: {solution.total_tardiness}")

Results

When you run the above code, it will output the scheduled jobs along with their completion times and tardiness values, as well as the total tardiness across all scheduled jobs.

Job 1: Completion Time = 2, Tardiness = 0
Job 2: Completion Time = 5, Tardiness = 0
Job 3: Completion Time = 9, Tardiness = 1
Job 4: Completion Time = 10, Tardiness = 5
Job 5: Completion Time = 15, Tardiness = 5
Total tardiness: 11