Objectives in PyJobShop

Open In Colab

If you’re using this notebook in Google Colab, be sure to install PyJobShop first by executing pip install pyjobshop in a cell.

This notebook provides an overview of the different objectives in PyJobShop via simple scheduling problems, specifically:

  • How to set different objective weights using PyJobShop’s Model interface,

  • How to set the job weights with Model,

  • Giving an overview of the different objectives in PyJobShop,

  • How the objective value of a schedule is calculated in PyJobShop.

For illustration, let’s add a single machine and some data to a PyJobShop Model. We add two jobs, each consisting of one task. The jobs’ due dates differ. Furthermore, switching from the second task to the first task has a setup time of 10 time units.

[1]:
from pyjobshop import Model

model = Model()
machine = model.add_machine()

jobs = [
    model.add_job(due_date=12),
    model.add_job(due_date=1),
]
tasks = [model.add_task(job=job) for job in jobs]
modes = [model.add_mode(task, machine, duration=1) for task in tasks]
model.add_setup_time(machine, tasks[1], tasks[0], 10)
[1]:
SetupTime(machine=0, task1=1, task2=0, duration=10)

Let’s solve this model and inspect the results.

[2]:
result = model.solve(display=False)

print("Solver status =", result.status)
print("Objective value =", result.objective)

for idx, task in enumerate(result.best.tasks):
    print(f"Task {idx} start time =", task.start)
Solver status = SolveStatus.OPTIMAL
Objective value = 2.0
Task 0 start time = 0
Task 1 start time = 1

By default, the makespan is minimized. The due dates are thus ignored. Consequently, it is best to avoid setup time and prioritize task one over task two.

Setting different objective weights

Now, let’s try to meet the due dates by setting the (weighted) total tardiness as an objective and solve it again.

Warning: Tardiness-based objective

It is necessary that the due_date is specified for all jobs when using a tardiness-based objective.

[3]:
model.set_objective(weight_total_tardiness=1)  # all other weights are 0
result = model.solve(display=False)

print("Solver status =", result.status)
print("Objective value =", result.objective)
print("Makespan =", result.best.makespan)

for idx, task in enumerate(result.best.tasks):
    print(f"Task {idx} start time =", task.start)
Solver status = SolveStatus.OPTIMAL
Objective value = 0.0
Makespan = 12
Task 0 start time = 11
Task 1 start time = 0

This time, the solver chooses to start with task two to meet its due date precisely. The setup time is taken for granted, as also the due date of task one is met. This leads to 0 tardiness, but a makespan of 12.

Info: Difference between deadline and due_date

The deadline of a job is strict, leading to infeasibility when it is not met. In contrast, a job’s due_date is not strict and can be violated. Violation of a due_date is taken into account in tardiness-based objectives.

Suppose you are interested in minimizing the weighted sum of the makespan and the total tardiness. All other weights are zero. You can set both objectives with different weights, as shown next.

[4]:
model.set_objective(weight_makespan=1, weight_total_tardiness=2)
result = model.solve(display=False)

print("Solver status =", result.status)
print("Objective value =", result.objective)
print("Makespan =", result.best.makespan)

for idx, task in enumerate(result.best.tasks):
    print(f"Task {idx} start time =", task.start)
Solver status = SolveStatus.OPTIMAL
Objective value = 4.0
Makespan = 2
Task 0 start time = 0
Task 1 start time = 1

Here, the total tardiness is twice as important as the makespan. As a result, the solver starts with task one again, leading to an objective value of 1 * 2 + 2 * 1 = 4 (weight_makespan * makespan + weight_total_tardiness * total_tardiness).

Different job weights

In practice, jobs can have different priorities. For example, you would like to meet the due date of a new customer to show your best service. To differentiate between the importance of jobs, you can set job weights as shown in the following single machine example with two jobs. Job one is three times as important as job two in this example.

[5]:
model = Model()

machine = model.add_machine()

jobs = [
    model.add_job(weight=3, due_date=2),
    model.add_job(weight=1, due_date=1),
]
tasks = [model.add_task(job=job) for job in jobs]
modes = [
    model.add_mode(tasks[0], machine, duration=2),
    model.add_mode(tasks[1], machine, duration=1),
]
model.set_objective(weight_total_tardiness=1)
[5]:
Objective(weight_makespan=0, weight_tardy_jobs=0, weight_total_flow_time=0, weight_total_tardiness=1, weight_total_earliness=0, weight_max_tardiness=0, weight_max_lateness=0, weight_total_setup_time=0)

When solving this model, the tardiness of job one counts three times as much as the tardiness of job two towards the weighted total tardiness. When the job weights were equal, it would be best to schedule job two first (giving a total tardiness of 1 from job one). However, because job one is three times as important, the solver chooses to start with job one, as shown in the following.

[6]:
result = model.solve(display=False)

print("Solver status =", result.status)
print("Objective value =", result.objective)

for idx, task in enumerate(result.best.tasks):
    tardiness = max(task.end - jobs[idx].due_date, 0)

    print(f"Task {idx} start time =", task.start)
    print(f"Task {idx} tardiness =", tardiness)
Solver status = SolveStatus.OPTIMAL
Objective value = 2.0
Task 0 start time = 0
Task 0 tardiness = 0
Task 1 start time = 2
Task 1 tardiness = 2

Other objectives in PyJobShop

PyJobShop supports the minimization of the weighted sum of the following objectives:

  • Makespan: the finish time of the latest task.

  • Number of tardy jobs: the weighted sum of all tardy jobs, where a job is tardy when it does not meet its due date.

  • Total flow time: the weighted sum of the length of stay in the system of each job, from their release date to their completion.

  • Total tardiness: the weighted sum of the tardiness of each job, where the tardiness of a job is the difference between its completion time and its due date, where tardiness is 0 when it is completed before its due date.

  • Total earliness: the weighted sum of the earliness of each job, where the earliness of a job is the difference between its due date and its completion time, where earliness is 0 when its completion time is after its due date.

  • Maximum tardiness: the weighted maximum tardiness of all jobs.

  • Maximum lateness: the weighted maximum lateness of all jobs. The lateness of a job is the difference between its completion time and its due date (in contrast to tardiness, it can be smaller than 0).

  • Total setup time: the sum of all sequence-dependent setup times between consecutive tasks on each machine.

The objective weights are captured in the Objective class; for more details, see its API documentation.

General calculation of a schedule’s objective value

We now explain how the objective value is calculated in general. To that end, let \(w_j\) denote the weight of job \(j\) and let \(w_{\text{obj}}\) denote the weight of objective \(\text{obj}\) (see the previous section for an overview of all objectives). We denote the contribution of job \(j\) to the objective \(\text{obj}\) in schedule \(s\) as \(\text{contribution}(j, \text{obj}, s)\). The value of the makespan of schedule \(s\) is denoted by \(\text{makespan}(s)\) and the value of the total setup time of schedule \(s\) is denoted by \(\text{TST}(s)\). The general objective value for schedule \(s\) is calculated in PyJobShop as follows: \begin{equation} \text{objective}(s) = w_{\text{makespan}} \cdot \text{makespan}(s) + w_{\text{TST}} \cdot \text{TST}(s) + \sum_{\text{obj} \neq \text{makespan}, \text{TST}} w_{\text{obj}} \cdot \sum_{j} w_j \cdot \text{contribution}(j, \text{obj}, s). \end{equation} The following simple example of two jobs, with given weights, release date, and start and end times, shows how the objective components are calculated and combined in the different objective values. In particular, the following table shows the objective component values for the two jobs.

Job

Weights

Release date

Start time

End time

Due date

Tardy? (1=yes, 0=no)

Flow time

Tardiness

Earliness

Lateness

1

3

1

2

4

3

1

3

1

0

1

2

1

0

0

2

4

0

2

0

2

-2

Based on these objective component values, the following table shows the different objective values for the example.

Objective

Value

Makespan

4

Number of tardy jobs

3

Total flow time

11

Total tardiness

3

Total earliness

2

Maximum tardiness

3

Maximum lateness

3

Total setup time

0

Conclusion

This concludes the demonstration of the different objectives in PyJobShop. Using simple examples, we showed how to focus on different objectives and set their weights. Also, we showed how to set the weights of jobs to prioritize them. For more details about Objective, see the API documentation.