0

In the example of the job shop problem (https://developers.google.com/optimization/scheduling/job_shop), the result of the optimization provides that all three jobs to be optimized are simultaneously active from time 3 to time 8. The problem I have to solve instead requires that no more than a certain number of jobs can be active at the same time.

I tried to add these constraints to the job shop code:

for job_id1, job1 in enumerate(jobs_data):
        model.Add(
            sum(
                [
                    model.NewBoolVar(f"""{
                        all_tasks[job_id1, len(job1)-1].end} > {all_tasks[job_id2, 0].start
                    }""") 
                    for job_id2, job2 in enumerate(jobs_data)
                ]
                +
                [
                    model.NewBoolVar(f"""{
                        all_tasks[job_id1, 0].start} < {all_tasks[job_id2, len(job2)-1].end
                    }""") 
                    for job_id2, job2 in enumerate(jobs_data)
                ]                
            ) <= 2
        )

which are correct from the syntax point of view (therefore they do not return any error messages) but have no effect on the simulation result, which continues to be always the same, that is (the solution I get is a little different from the one that is present in the job shop post):

    Solution:
    Optimal Schedule Length: 11.0
    Machine 0: job_1_task_0   job_0_task_0   
               [0,2]          [2,5]          
    Machine 1: job_2_task_0   job_0_task_1   job_1_task_2   
               [0,4]          [5,7]          [7,11]         
    Machine 2: job_1_task_1   job_2_task_1   job_0_task_2   
               [2,3]          [4,7]          [7,9]

while I would like a solution like this:

    Solution:
    Optimal Schedule Length: 14.0
    Machine 0: job_1_task_0   job_0_task_0   
               [0,2]          [7,10]          
    Machine 1: job_2_task_0   job_1_task_2   job_0_task_1      
               [0,4]          [4,8]          [10,12]         
    Machine 2: job_1_task_1   job_2_task_1   job_0_task_2   
               [2,3]          [4,7]          [12,14]

in which job 0 starts only after job 2 has finished, thus ensuring that there are no more than two jobs active at the same time (obviously this implies that the overall time increases from 11 to 14 but in my real problem that I have to resolve I am obliged to do so).

My idea was to compare each job with all the other jobs and, for each job (let's call it "job_id1"), if its end_time of the last task is greater than the start_time of the first task of another job or his start_time of the first task is greater than the end_time of the last task of the same other job so I add a 1 in the form of a boolean variable in a list (the reason why I had to use NewBoolVar is explained in this post How can I use CpModel to make three 1s appear in a 1D array ? (or-tools)). When the list is full I do the sum of all the 1's in the list and this gives me a measure of how many jobs are superimposed on the job "job_id1" and I impose that this sum have to be less or equal than a certain value, for example 2. Adding this constraint for all jobs in theory should ensure that there are never more than N simultaneously active jobs.

Any suggestions are greatly appreciated, thank you all.

=================================================================

UPDATE:

Thanks to @sascha and Hakan Kjellerstrand (http://www.hakank.org/google_or_tools/) and, in particular to the example http://hakank.org/or_tools/furniture_moving_add_cumulative_sat.py, I replaced the previous approach with the following one based on the AddCumulative constraint

n = 3
duration = [7, 7, 7]
demand = [1, 1, 1]
upper_limit = 14

start_times = [
    model.NewIntVar(0, upper_limit, "start_times[%i]" % i) for i in range(n)
]
end_times = [
    model.NewIntVar(0, upper_limit * 2, "end_times[%i]" % i) for i in range(n)
]
end_time = model.NewIntVar(0, upper_limit * 2, "end_time")

intervals = [model.NewIntervalVar(start_times[i],duration[i],end_times[i],f"interval[{i}]")  for i in range(n)]

# number of needed resources, to be minimized
num_resources = model.NewIntVar(1, 2, "num_resources")

model.AddCumulative(intervals, demand, num_resources)

model.Add(num_resources <= 2)

model.Minimize(num_resources)

that should be better and modify a little bit the output results in this one:

Solution:
Optimal Schedule Length: 1.0
Machine 0: job_1_task_0   job_0_task_0   
           [0,2]          [2,5]          
Machine 1: job_2_task_0   job_1_task_2   job_0_task_1   
           [0,4]          [4,8]          [8,10]         
Machine 2: job_1_task_1   job_2_task_1   job_0_task_2   
           [2,3]          [4,7]          [10,12] 

where job_0_task_1, job_1_task_2 and job_0_task_2 have now different start_time and end_time.

Unfortunately this is not yet the result I was hoping to achieve since more than 2 jobs are simultaneously active between instant 2 and instant 7 although I have indicated a number of resources less than or equal to 2 (in the problem I have to solve, each resource is associated with only one job), so I am asking again if anyone can help me understand why, thank you.

  • 1
    (Without looking too hard at your code:) Are you aware of [AddCumulative](https://developers.google.com/optimization/reference/python/sat/python/cp_model#addcumulative)? CP-SAT supports some incredibly powerful scheduling constraints (like AddCumulative) which internally do some pretty powerful reasoning not easy to achieve with *decomposition approaches* (*glue primitives together*) like you tried. You might look into any Constraint-Programming / Scheduling tutorial as Disjunctive/Cumulative are THE two constraint-superstars there. – sascha May 15 '22 at 11:31
  • Thanks sascha, it might be the right idea, but I forgot to make it clear that I am a newbie in constraint programming and in the use of the OR-Tools library so I really need some piece of sample code that can help me understand how to translate my constraint so that it can be correctly interpreted by the library. Could you give me some more suggestions or an example similar to what I need to do? Thank you so much again for your kind reply – Vincenzo De Leo May 15 '22 at 12:54
  • I would start by using Githubs search in or-tools repository for examples: [Github: "AddCumulative (Python)"](https://github.com/google/or-tools/search?l=Python&q=AddCumulative). In general, all you need to do is to 1) Select the set of IntervalVars which are to be restricted (`intervals`) 2) Weight them: In your case probably unit-weights, a list of ones (`demands`) and 3) Set a constant which is the upper-bound of the vector-product (`capacity`). Meaning: `sum(intervals[i] * weights[i] for i in range(N) if i_is_active_at_time_t) <= capacity` is enforced at each discrete point in time. – sascha May 15 '22 at 13:06
  • If you are new to *cumulative*, you will probably also like [Global Constraint Catalog: 5.96. cumulative](https://sofdem.github.io/gccat/gccat/Ccumulative.html). – sascha May 15 '22 at 13:10
  • Thanks again @sascha, I spent these days to study the AddCumulative constraints and try to understand how to implement your kind suggestion. I posted an update where I tried to implement this approach but there is probably still something that is not clear to me about how this type of constraint is used... – Vincenzo De Leo May 21 '22 at 10:49

0 Answers0