Professional Documents
Culture Documents
Vediamo un classico: dovete fare con Microsoft Project un diagramma di GANTT con
risorse, task in cascata o parallelizzabile e le milestone con ogni opportuna data e
descrizione; poi vi hanno richiesto il diagramma di Pert per comprendere i task critici.
Sapete l'inizio e la fine desiderata, quanto serve per ogni attività, quante persone, ma
occorre far entrare il tutto nel periodo richiesto. E' possibile o impossibile? Come e
quali attività si devono parallelizzare? Quello ottenuto è il meglio possibile o esistono
anche altre soluzioni?
Al di là di ulteriori elementi che dipendono dal tipo di lavoro in gioco, stiamo parlando
di un problema di pianificazione o scheduling, che rientra tra i problemi NP-completi!
Con un grafo, quindi, si possono formulare varie tipologie di problemi. Per grafo pesato
orientato intendiamo una struttura G(N, A), con N insieme dei nodi e cardinalità | N |,
ed A insieme degli archi e cardinalità | A |.
L'insieme degli archi, A, è caratterizzato da un orientamento da nodo origine ad uno
destinazione i,j.
Per risolvere, nel miglior modo possibile, un tale problema sono possibili varie
strategie:
algoritmi tipici della teoria dello scheduling
algoritmi legati ai grafi
Un algoritmo tipico della teoria dello scheduling è quello MST (Minimum Spanning
Tree) o minimo albero ricoprente. Nel MST il problema è definito nel seguente modo:
sia dato un grafo orientato pesato G(N, A), con un insieme di interi senza segno
definito sugli archi, allora l'obiettivo è quello di determinare una struttura che
connetta tutti i nodi del grafo con archi di peso minimo.
Un albero ricoprente di costo minimo è tipicamente una struttura con un unico nodo
origine, detto radice, ed una serie di rami; il "cammino" da individuare è composto
dall'insieme dei nodi in una sequenza tale che la somma dei pesi sui rami sia la minima
possibile.
E' evidente che l'MST ci porta a minimizzare il "makespan". Poichè abbiamo a che fare
con un grafo a numero di nodi fissi, (ricordate cosa abbiamo detto circa il problema di
Steiner?) allora è possibile usare l'algoritmo di Kruskal (vi rimando al blog precedente
per tale algoritmo).
Ma dobbiamo aprire una parentesi sui labirinti per comprendere il punto 2 precedente
e concludere il discorso sullo scheduling. Quindi c'è un interrupt e salvate un attimo il
contesto sullo stack.
L'algoritmo di Tremaux consiste nel seguire un percorso, scelto a caso all'interno del
labirinto, fino a raggiungere un incrocio, marcando la via che è stata percorsa fino a
quel momento (nel caso in cui il corridoio conduca a un vicolo cieco è necessario
tornare indietro fino all'incrocio precedente, marcando la via all'andata e al ritorno).
Quando si giunge a un incrocio di più corridoi si prende preferibilmente una via che
non è stata segnata come percorsa in precedenza, e se ciò non è possibile si prende
una via percorsa una sola volta. In ogni caso non è permesso scegliere una via che è
stata già marcata due volte. Iterando il procedimento per ogni incrocio che si trova
sul proprio percorso, l'algoritmo permette di raggiungere l'uscita (o se il labirinto non
ha altre uscite oltre a quella imboccata per entrare, di tornare all'entrata).
Mi direte ma nel labirinto non c'è parallelismo ... non è detto: dipende da quante
persone lo percorrono contemporaneamente.
Una volta individuato il labirinto occorre iniziare a vedere quale oggetto/task occorre
estrarre per arrivare alla successiva lavorazione. Occorre stare attenti a semplificare
il problema altrimenti si arriva ad aggiungere molte condizioni e vincoli che aumentano
il costo dell'elaborazione. La strategia di scelta degli oggetti/task deve essere,
difatti, quella che è in relazione alla sequenza ottima che minimizza il makespan, il
peso degli oggetti e i tempi di computazione.
Non si può usare prima la tecnica dello zaino e poi la tecnica del MST perché si
arriverebbe ad una soluzione non necessariamente la migliore in termini di
schedulazione; per cui la soluzione migliore è di fare prima un algoritmo che
restituisca il makespan a cui si applica la tecnica dello zaino.
Supponiamo che ci siano n task che m macchine possono lavorare in parallelo, col
vincolo di preemption, e che un task startato su una macchina può continuare su una
macchina differente, a seguito di interruzione sulla macchina precedente.
Cmax = max(1<=j<=n) Cj
Mentre xij= frazione di task j lavorata sulla macchina i, col vincolo che Sum(i=1,m, xji)
= 1 (Che esprime che una sola macchina per volta lavora un task: non è consentita la
preemption). In particolare xji={0,1}.
Riassumendo la formulazione in LP è:
min Cmax
Sum(j=1,n,pj*xji) <= Cmax
Sum(i=1,m, xij) = 1
xji>=0
E' vera la seguente affermazione: Sia x* una soluzione che soddisfa tutte le
condizioni e vincoli, allora x* è la soluzione ottima.
Il problema, come già più volte evidenziato, può essere formulato nel seguente modo.
Si hanno a disposizione n oggetti, ciascuno con valore vi e costo ci; nella fattispecie,
tali parametri possono essere rivisti come tempo di processamento e peso. In questo
caso il problema può essere espresso come massimizzazione del valore, nel rispetto
della sequenza makespan e minimizzazione del peso, visto che dovranno essere
effettuati diversi viaggi.
max F = Sum(i=1,n,vi*xi)
Sum(i=1,n,ci*xi)<=C con xi={0,1}
Si possono risolvere problemi molto complessi (in questi ambiti basta poco per
complicarli) e realizzare sistemi con Database Oracle, PL/SQL e con un motore
risolutore GPLK. Nel seguito esamineremo un problemino di schedulazione semplice.
Problema di schedulazione
In un impianto di produzione di tutti i giorni un certo numero di personale è
necessario. Il personale può essere essere assunto per un minimo fino ad un numero
massimo di giorni consecutivi, richiede un numero minimo di giorni di ferie prima di
poter essere nuovamente utilizzati. Il compito è di trovare
l'orario di lavoro riducendo al minimo salario totale da pagare.
set L, dimen 2;
set W, dimen 5;
# names of workers
set V := setof{(v, b, c, d, e) in W} v;
# periods
# wage
# workday
# leaveday
minimize wage :
sum{b in B, (v,s,d) in O} x[v,s,d] * wd[b,v,s,d] * wa[v];
s.t. j1{b in B, v in V} :
sum{(v,s,d) in O} x[v,s,d] * ( wd[b,v,s,d] + ld[b,v,s,d] ) <= 1;
# do all jobs
s.t. ja{b in B} :
sum{(v,s,d) in O} x[v,s,d] * wd[b,v,s,d] >= sum{(b, w) in L} w;
solve;
# output solution
for {v in V}
printf "| %-10s", v;
printf "\n";
for {v in V}
printf "+-%-10s", '----------';
printf "\n";
for {b in B}
{
printf "%9i ", b;
for {v in V}
printf "\n";
}
printf "\n";
data;
# workload
( 1, 3)
( 2, 1)
( 3, 1)
( 4, 1)
( 5, 1)
( 6, 2)
( 7, 2)
( 8, 2)
( 9, 3)
(10, 2)
(11, 2)
(12, 2)
(14, 1)
(15, 2)
(17, 3)
(18, 3)
(19, 2)
(21, 3)
(23, 1)
(24, 3)
(25, 3)
(26, 3)
(27, 2)
(28, 1)
(29, 1)
(30, 2)
(31, 1)
(32, 3)
(33, 2)
(35, 3)
(36, 3)
(37, 1)
(38, 3)
(39, 2)
(40, 3)
(43, 1)
(44, 1)
(45, 2)
(46, 2)
(47, 3)
(48, 2)
(49, 1)
(50, 2);
# workers
( 'Anna', 5, 8, 2, 470 )
( 'Isabel', 5, 8, 2, 500 )
( 'Jack', 1, 8, 2, 1000 )
( 'John', 5, 8, 2, 600 )
( 'Lisa', 3, 5, 3, 640 );
end;
Un consiglio. E' ovvio che lo strumento vi calcola il tutto in base ai dati che gli avete
fornito e funziona benissimo quando la durata del tempo è ben fissata da regole, come
nell'organizzazione di orari, turni etc.