:: Ricerca Operativa - PLI - 3 novembre 2004 ::
Una macchina utensile deve svolgere una serie di operazioni (jobs). Di ogni job è nota la durata. I jobs ovviamente non si possono sovrapporre: per poterne iniziare uno dev’essere finito quello precedente.
Per ogni job sono noti gli istanti iniziale e finale di una finestra temporale all’interno della quale deve iniziare e terminare la lavorazione.
Sull’obiettivo i responsabili della pianificazione della produzione discutono; si vorrebbero considerare diverse possibilità.
* Un primo obiettivo è quello di minimizzare il tempo complessivo necessario ad eseguire tutti i jobs.
Formulare il problema, classificarlo e risolverlo nei tre casi indicati con i dati del file SCHEDULE.TXT.
I jobs sono 7. ======================================== Tabella 1: Tempi di lavorazione Job Tempo 1 10 2 14 3 21 4 18 5 4 6 23 7 35 ======================================== Tabella 2: Finestre temporali Job Inizio Fine 1 15 50 2 0 80 3 0 95 4 10 75 5 5 30 6 13 130 7 18 120
Allora, quello che abbiamo sono:
Quello che dobbiamo scoprire è in che istante far partire il j-esimo Steve jobs:
È continua non negativa perché qualsiasi istante dopo lo 0 va bene.
Il primo vincolo riguarda la finestra temporale. Il testo ci dice che un job ha una finestra temporale che gli è stata assegnata, e deve iniziare e terminare dentro di essa. Quindi, l'istante iniziale del job deve essere >= all'istante di inizio della sua finestra temporale, e l'istante finale deve essere <= all'istante finale della sua finestra temporale:
tj + dj mi dà l'esatto istante in cui finisce il job, perché sommo l'istante in cui comincia con la sua durata, la quale è un dato.
Torniamo a parlare delle variabili. Quelle di sopra erano quelle più facilmente riconoscibili leggendo il testo. Però, approfondendo il discorso salta fuori che ci sono altre cose di cui occorre tenere traccia.
La prima è che i job non si possono sovrapporre. Qui entrano in ballo le variabili binarie: prendo una bella tabella N*N, in cui la casella (i,j) vale 1 se il job i precede il job j, 0 altrimenti. La tabella la chiamiamo y:
La nuova variabile y introduce un'altro problema: se il lavoro i precede il lavoro j, allora vuol dire che ti + di <= tj, cioè l'istante di fine del lavoro i deve precedere l'istante di inizio del lavoro j. Al contrario, se j precede i, allora tj + dj <= ti:
Per rappresentare queste condizioni, usiamo un trucco barbino che non sarebbe mai venuto in mente in sede d'esame. Prendiamo una variabile M, di valore molto grande, e la usiamo in questo modo:
In pratica, moltiplicando M per il valore di y in corrispondenza di i e ''j, è come se dicessimo:
C'è anche un'altra cosa da dire: se il job i precede j, ovvero ha un 1 nella cella yi,j, allora NON deve accadere che j preceda i, cioè che ci sia un 1 ANCHE nella cella yj,i:
Il tempo impiegato è dato da SOMMA(j): t(j) + i(j), per ogni lavoro: istante iniziale più durata di ogni lavoro. Vogliamo che questa somma sia minima, e quindi:
min = delta;
delta >= tj + dj, per ogni j
Per il tempo medio di completamento, prendo tutti i tempi di completamento e ne faccio la media:
min = 1/7 * @sum(jobs(j): t(j) + d(j));
Un job deve terminare, come scritto nei vincoli, entro l'istante finale della sua finestra temporale. Qui vogliamo che la media delle differenze tra istante finale effettivo e istante finale massimo consentito (fine della finestra) sia massima:
max = 1/7 * @sum(jobs(i): b(i) - a(i) + t(i));
! 3 novembre 2004 - Scheduling; model: sets: jobs /1..7/: durata, iniziowin, finewin, t; schedule (jobs,jobs): y; endsets data: durata = 10 14 21 18 4 23 35; iniziowin = 15 0 0 10 5 13 18; finewin = 50 80 95 75 30 130 120; M = 1000; enddata ! Ci sono 3 funzioni obiettivo: ! 1) minimizzare il tempo complessivo necessario al completamento di ! tutti i job (min makespan) ! Si tratta di minimizzare il massimo tempo, quindi un min max. ! Introduco una variabile ausiliaria delta; !min = delta; !@for(jobs(j): delta >= t(j) + durata(j)); ! 2) Minimizzare il tempo medio di completamento; !min = 1/7 * @sum(jobs(i): t(i) +durata(i)); ! 3) Massimizzare l'anticipo medio con cui i jobs terminano il loro lavoro; max = 1/7 * @sum(jobs(i): finewin(i) - (iniziowin(i) + durata(i))); ! Vincoli sulle finestre temporali; @for (jobs(j): t(j) >= iniziowin(j)); @for (jobs(j): t(j) + durata(j) <= finewin(j)); ! Vincoli di non sovrapposizione; ! Ho un vincolo per ogni coppia di job => 2 cicli for innestati; @for (jobs(i): @for(jobs(j) | i #NE# j: t(i) + durata(i) <= t(j) + M * (1 - y(i,j)) ) ); @for (jobs(i): @for(jobs(j) | i #NE# j: t(j) + durata(j) <= t(i) + M * y(i,j) ) ); @for (schedule(i,j) | i #NE# j: y(i,j) + y(j,i) = 1); ! Per evitare di fare due volte lo stesso conto, scrivo LT: lesser than, così ! fa il giro una volta sola; !@for (schedule(i,j) | i #LT# j: y(i,j) = 1); @for(schedule(i,j): @bin(y(i,j))); end