ALGORITMI EURISTICI PER PROBLEMI DI TAGLIO ...Sommario Argomento di questa tesi `e lo sviluppo e...

113
ALMA MATER STUDIORUM - UNIVERSITA’ DI BOLOGNA FACOLTA’ DI INGEGNERIA Corso di Laurea in INGEGNERIA INFORMATICA Insegnamento: OTTIMIZZAZIONE COMBINATORIA ALGORITMI EURISTICI PER PROBLEMI DI TAGLIO BIDIMENSIONALE Tesi di Laurea di: Fabio Ciani Relatore: Chiar.mo Prof. Paolo Toth Correlatori: Prof. Alberto Caprara Ing. Enrico Malaguti ANNO ACCADEMICO 2004-2005

Transcript of ALGORITMI EURISTICI PER PROBLEMI DI TAGLIO ...Sommario Argomento di questa tesi `e lo sviluppo e...

ALMA MATER STUDIORUM - UNIVERSITA’ DI BOLOGNA

FACOLTA’ DI INGEGNERIA

Corso di Laurea in INGEGNERIA INFORMATICA

Insegnamento: OTTIMIZZAZIONE COMBINATORIA

ALGORITMI EURISTICI PER PROBLEMI

DI TAGLIO BIDIMENSIONALE

Tesi di Laurea di:

Fabio Ciani

Relatore:

Chiar.mo Prof. Paolo Toth

Correlatori:

Prof. Alberto Caprara

Ing. Enrico Malaguti

ANNO ACCADEMICO 2004-2005

Sommario

Argomento di questa tesi e lo sviluppo e l’implementazione di due algoritmi

euristici per problemi vincolati di taglio bidimensionale, ortogonale, non a

ghigliottina. Il problema consiste nel tagliare pezzi rettangolari da un rettan-

golo piu grande in modo da massimizzare il valore totale dei pezzi tagliati.

Il problema e classificabile, nell’ambito della teoria della complessita compu-

tazionale, come NP − Hard e non sono noti, per questo tipo di problemi,

algoritmi in grado di ottenere una soluzione ottima in un tempo polinomiale

per qualunque istanza del problema.

Si e dunque progettato, e successivamente implementato sfruttando il lin-

guaggio di programmazione C, prima un algoritmo euristico che utilizza la

tecnica nota come GRASP (greedy randomized adaptive search procedure)

e successivamente un altro algoritmo euristico che sfrutta la tecnica del Ta-

bu Search, per ottenere ”buone” soluzioni in tempi ragionevoli. Durante la

realizzazione sono state tenute in considerazione diverse strategie per le fasi

che costituiscono entrambi gli algoritmi e sono state effettuate diverse scelte

per la ricerca di parametri critici. Infine sono stati eseguiti molti esperimenti

computazionali su un gran numero di istanze di tipo eterogeneo, sia per se-

lezionare le migliori alternative, sia per comparare l’efficienza degli algoritmi

con altri gia esistenti.

Parole chiave: taglio, bidimensionale, euristico, euristici, tabu search

i

Indice

Sommario i

1 Introduzione 7

1.1 Origini, applicazioni e classificazioni del problema di taglio . . 7

1.2 Formulazione del problema . . . . . . . . . . . . . . . . . . . . 10

1.3 Esempi grafici di risoluzione . . . . . . . . . . . . . . . . . . . 12

2 Tecniche euristiche 15

2.1 Sviluppo di un algoritmo costruttivo . . . . . . . . . . . . . . 16

2.1.1 Inizializzazione di P , L e C . . . . . . . . . . . . . . . 16

2.1.2 Scelta del rettangolo . . . . . . . . . . . . . . . . . . . 17

2.1.3 Scelta del pezzo da tagliare . . . . . . . . . . . . . . . 17

2.1.4 Aggiornamento di P , L e C . . . . . . . . . . . . . . . 18

2.2 Esempio di applicazione dell’algoritmo . . . . . . . . . . . . . 20

3 Tecniche metaeuristiche 23

3.1 Ricerca Locale . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

3.2 Genetic Search . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3.3 Simulated Annealing . . . . . . . . . . . . . . . . . . . . . . . 31

3.4 Ant Colony Optimization . . . . . . . . . . . . . . . . . . . . . 34

3.5 GRASP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

3.6 Tabu Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

4 Sviluppo di un algoritmo GRASP 45

4.1 Fase Costruttiva . . . . . . . . . . . . . . . . . . . . . . . . . . 45

iii

4.1.1 Scelta del parametro δ . . . . . . . . . . . . . . . . . . 46

4.2 Fase di Miglioramento . . . . . . . . . . . . . . . . . . . . . . 47

4.3 Variazione dei vincoli dei pezzi . . . . . . . . . . . . . . . . . . 49

4.3.1 Aumento dei lower bound Pi . . . . . . . . . . . . . . . 49

4.3.2 Diminuzione degli upper bound Qi . . . . . . . . . . . 50

4.4 Scelta delle migliori strategie . . . . . . . . . . . . . . . . . . . 50

5 Sviluppo di un algoritmo Tabu Search 53

5.1 Definizione delle mosse . . . . . . . . . . . . . . . . . . . . . . 53

5.1.1 Riduzione di un blocco . . . . . . . . . . . . . . . . . . 54

5.1.2 Inserimento di un blocco . . . . . . . . . . . . . . . . . 56

5.2 Mosse da esaminare . . . . . . . . . . . . . . . . . . . . . . . . 59

5.3 Selezione della mossa . . . . . . . . . . . . . . . . . . . . . . . 60

5.4 Tabu list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

5.5 Intensificazione e diversificazione . . . . . . . . . . . . . . . . 61

5.6 Variazione dei vincoli dei pezzi . . . . . . . . . . . . . . . . . . 62

5.7 Path relinking . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

5.8 Scelta delle migliori strategie . . . . . . . . . . . . . . . . . . . 65

6 Implementazione 67

6.1 Strutture dati . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

6.1.1 Lista dei pezzi P . . . . . . . . . . . . . . . . . . . . . 68

6.1.2 Lista dei rettangoli L . . . . . . . . . . . . . . . . . . . 68

6.1.3 Lista dei blocchi C . . . . . . . . . . . . . . . . . . . . 69

6.1.4 Lista delle soluzioni d’elite E . . . . . . . . . . . . . . . 69

6.1.5 Lista dei tabu T . . . . . . . . . . . . . . . . . . . . . 70

6.1.6 Lista delle frequenze dei pezzi F . . . . . . . . . . . . . 70

6.2 Caricamento dei dati iniziali . . . . . . . . . . . . . . . . . . . 70

6.3 Schema dell’algoritmo GRASP . . . . . . . . . . . . . . . . . . 71

6.4 Funzioni dell’algoritmo GRASP . . . . . . . . . . . . . . . . . 73

6.5 Schema dell’algoritmo Tabu Search . . . . . . . . . . . . . . . 75

6.6 Funzioni dell’algoritmo Tabu Search . . . . . . . . . . . . . . . 78

iv

6.7 Funzioni comuni ai due algoritmi . . . . . . . . . . . . . . . . 82

7 Test e risultati computazionali 91

7.1 Tipi di test . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

7.2 Risultati computazionali . . . . . . . . . . . . . . . . . . . . . 93

7.2.1 Confronto tra gli algoritmi GRASP . . . . . . . . . . . 93

7.2.2 Confronto tra gli algoritmi Tabu Search . . . . . . . . 99

7.2.3 Confronto tra le due tecniche . . . . . . . . . . . . . . 104

Conclusioni 105

Possibili sviluppi futuri 107

Bibliografia 109

v

Capitolo 1

Introduzione

1.1 Origini, applicazioni e classificazioni del

problema di taglio

Quando dei pezzi piccoli sono ricavati da pezzi piu grandi, due problemi

insorgono. Il primo riguarda la scelta della dimensione dei pezzi da tagliare

(assortment problem). Il secondo riguarda come ricavare i pezzi piccoli da

quelli grandi in modo da minimizzare il materiale scartato (trim loss problem)

(Hinxman, 1979). I pezzi piccoli sono spesso chiamati order list, mentre i

pezzi da tagliare, vengono chiamati stock material. La combinazione dei due

problemi e conosciuta come problema di taglio (cutting stock problem). Il

problema di taglio (CSP) insorge in molte grandi industrie dove fogli o lastre

devono essere tagliati in pezzi piu piccoli. Materiali tipici sono il vetro, il

legno, l’acciaio, la plastica e i prodotti tessili (Dyckhoff, 1990).

La prima formulazione del CSP risale al 1939 e fu realizzata da Kanto-

rovich, anche se venne pubblicata in Inghilterra solo nel 1960 (Kantorovich,

1960). La ricerca scientifica inizio solo allora e, fin da subito, si diffusero un

gran numero di metodi e risoluzioni. I problemi CSP possono essere clas-

sificati utilizzando lo schema proposto da Dyckhoff. Questa classificazione

prevede quattro diverse categorie:

7

1. Dimensione

• N) Numero delle dimensioni

2. Tipo di assegnamento

• B) Utilizzo di tutto il pezzo grande e di una selezione dei pezzi

piccoli

• V) Utilizzo di una parte del pezzo grande e di tutti i pezzi piccoli

3. Assortimento dei pezzi grandi da tagliare

• O) Un solo pezzo grande

• I) Molti pezzi grandi tutti della stessa dimensione

• D) Molti pezzi grandi di dimensioni diverse

4. Assortimento dei pezzi piccoli

• F) Pochi pezzi di dimensioni diverse

• M) Molti pezzi con molte dimensioni diverse

• R) Molti pezzi con poche dimensioni diverse

• C) Molti pezzi delle stesse dimensioni

La caratteristica piu importante e la dimensione. Dyckhoff suddivise

i problemi in monodimensionali, bidimensionali, tridimensionali e multidi-

mensionali. Nel caso monodimensionale, una sola dimensione e rilevante ai

fini della soluzione. Nei problemi bidimensionali, invece, i pezzi piccoli pos-

sono essere posizionati in maniera libera all’interno del pezzo da tagliare. Il

caso piu semplice si verifica quando i pezzi piccoli e il pezzo grande sono tutti

di forma rettangolare. Se i pezzi possono essere tagliati solamente con i lati

paralleli a quelli del pezzo grande, si parla di problema di tipo ortogonale

(orthogonal). Infine, se il taglio deve avvenire lungo l’intera lunghezza del

8

pezzo da tagliare (o di sue sottoparti formate da tagli precedenti), il problema

e detto ”a ghigliottina” (guillotine).

Diversi tipi di taglio sono mostrati nella Figura 1, dove le parti in grigio

rappresentano gli sprechi di materiale. In particolare la Figura 1(a) mostra

il caso del problema monodimensionale, dove la lunghezza dei pezzi e pre-

fissata, mentre le Figure 1(b) e 1(c) mostrano la differenza tra i problemi

bidimensionali a ghigliottina e quelli non a ghigliottina.

a) taglio monodimensionale

b) taglio bidimensionale a ghigliottina

c) taglio bidimensionale non a ghigliottina

Figura 1 Diversi tipi di taglio

9

Un’altra importante caratteristica, che influenza la complessita del pro-

blema, e il tipo di assegnamento. Per la classificazione dei vari CSP sono

necessarie almeno due categorie. Nella prima, tutto il pezzo da tagliare e

utilizzato, ma non tutti i pezzi piccoli possono essere ricavati. Nella seconda,

tutti i pezzi sono ritagliati, ma soltanto una parte del pezzo grande viene

utilizzata.

La terza caratteristica puo essere suddivisa in tre categorie a seconda

dell’assortimento dei pezzi grandi. Nel primo caso c’e un unico pezzo da

tagliare. Nel secondo, ci sono tanti pezzi grandi con le stesse dimensioni.

Infine, nel terzo caso, ci sono molti pezzi da tagliare con dimensioni differenti.

Per esempio, questo caso si verifica quando i pezzi da tagliare sono gli scarti

di tagli precedentemente eseguiti.

Una suddivisione simile puo essere fatta anche per l’assortimento dei pez-

zi piccoli. Nel primo caso ci sono pezzi piccoli di dimensioni diverse. Nel

secondo, ci sono molti pezzi piccoli, di cui molti di essi con dimensioni diffe-

renti. Nel terzo, ci sono molti pezzi piccoli, di cui molti di essi hanno le stesse

dimensioni. Infine, nell’ultimo caso, tutti i pezzi hanno le stesse dimensioni.

Poiche e stato dimostrato che anche i problemi di taglio piu semplici sono

NP − Hard, anche quelli piu complicati sono NP − Hard (Blazewicz et

al 1989; Fowler et al, 1981). Questo significa che e molto improbabile che

esista un algoritmo che risolva il CSP, in maniera ottimale, in un tempo

polinomiale. Per questo motivo, anziche cercare di trovare algoritmi esatti,

sin dalla sua formulazione, il CSP e stato affrontato sviluppando algoritmi

euristici per la sua risoluzione. Nelle prossime pagine verranno analizzati

diversi tipi di metodi euristici cercando di individuare quello che meglio si

adatta alla risoluzione del CSP.

1.2 Formulazione del problema

Il problema trattato da questa tesi e un problema di taglio bidimensionale,

ortogonale, non a ghigliottina, che consiste nel ricavare un insieme finito

10

di piccoli pezzi rettangolari a partire da un rettangolo base di dimensioni

prefissate, in modo da massimizzare il profitto.

Il rettangolo base R ha lunghezza L ed altezza W , mentre ogni pezzo

i da tagliare ha dimensioni (li,wi) e un valore indicato con vi. Il problema

consiste nel tagliare xi copie di ogni pezzo i in modo che 0 ≤ Pi ≤ xi ≤ Qi,

i = 1, ..,m e che il valore totale∑

i vixi sia massimizzato. Il massimo numero

di pezzi che possono essere tagliati e indicato con M =∑

i Qi.

I pezzi da tagliare sono soggetti a due ulteriori vincoli:

• I pezzi hanno un’orientazione fissata, cioe pezzi di dimensione (a, b) e

(b, a) non sono la stessa cosa.

• Ogni pezzo deve essere tagliato con i suoi lati paralleli a quelli del

rettangolo base (tagli ortogonali).

A seconda dei valori di Pi e Qi, si possono distinguere tre tipi di problemi:

1. Unconstrained : ∀i, Pi = 0, Qi = bL ∗W/li ∗ wic.

2. Constrained : ∀i, Pi = 0, ∃i, Qi < bL ∗W/li ∗ wic.

3. Doubly Constrained : ∃i, Pi > 0; ∃j, Qj < bL ∗W/lj ∗ wjc.

Definiamo l’efficienza di un pezzo i come ei = vi/(li ∗ wi); a seconda dell’ef-

ficienza dei pezzi si possono distinguere altre categorie di problemi:

1. Unweighted : ei = 1, ∀i. Il valore di ogni pezzo e uguale alla sua area.

2. Weighted : ei 6= 1, almeno per un pezzo i. Alcuni pezzi hanno un valore

che non corrisponde alla loro superficie, ma riflette altri aspetti come

la loro forma o la loro relativa importanza per i clienti.

E’ possibile ottenere un semplice upper bound per il problema risolvendo

il seguente knapsack problem, dove la variabile xi rappresenta il numero di

pezzi del tipo i che devono essere tagliati in piu rispetto al suo lower bound

Pi:

Maxm∑

i=1

vixi +m∑

i=1

viPi (1.1)

11

s.t.m∑

i=1

(liwi)xi ≤ LW −m∑

i=1

Pi(liwi) (1.2)

xi ≤ Qi − Pi, i = 1, ...m (1.3)

xi ≥ 0, integer, i = 1, ...m (1.4)

Alcuni autori hanno preso in considerazione il problema di tipo uncon-

strained : Tsai et al [3], Arenales e Morabito [4], Healy et al [5]. Tuttavia

questo genere di problema e di scarso interesse applicativo e pertanto, in que-

sta tesi, verranno sviluppati algoritmi euristici in grado di risolvere solamente

problemi di tipo constrained e doubly constrained.

1.3 Esempi grafici di risoluzione

Nella Figura 2 vediamo alcuni esempi grafici di risoluzione, con un rettangolo

base di dimensione (10,10) e m = 10 pezzi da tagliare (Figura 2(a)).

i li wi Pi Qi vi ei

1 3 2 1 2 7 1,166

2 7 2 1 3 20 1,428

3 4 2 1 2 11 1,375

4 6 2 0 3 13 1,083

5 9 1 0 2 21 2,333

6 8 4 0 1 79 2,468

7 4 1 1 2 9 2,25

8 1 10 0 1 14 1,4

9 3 7 0 3 52 2,476

10 4 5 0 2 60 3

La prima soluzione, di valore 268, e ottima per l’unconstrained problem (Fi-

gura 2(b)), la seconda, che produce un risultato di 247, e ottima per il con-

strained problem (Figura 2(c)), mentre la terza, il cui valore e 220, e ottima

per il doubly constrained problem (Figura 2(d)).

12

(a) pezzi da inserire (b) unconstrained, ottimo = 268

(c) constrained, ottimo = 247 (d) doubly constrained, ottimo = 220

Figura 2 Istanza 3 da Beasley [6]

13

Capitolo 2

Tecniche euristiche

Nell’affrontare un problema di ottimizzazione combinatoria possono essere

utilizzati vari approcci a seconda sia della difficolta specifica e delle dimen-

sioni del problema in esame, sia degli obiettivi reali che si vogliono ottenere.

Cosı, in quei casi in cui e necessario pervenire alla soluzione ottima di un

certo problema, si cerchera di sviluppare un algoritmo esatto che utilizzi

un approccio di enumerazione implicita delle istanze. Algoritmi esatti che

risolvono il problema di taglio bidimensionale sono stati sviluppati da Bea-

sley [6], Scheithauer e Terno [7], Hadjiconstantinou e Christofides [8], Fekete

e Schepers [9] e Caprara e Monaci [10].

Tuttavia questi metodi, nel caso di istanze di grosse dimensioni, non sono

in grado di fornire il risultato ottimo in un tempo accettabile. Per cui se non

e necessario individuare il risultato ottimo a tutti i costi, ma e sufficiente

avere una garanzia sul massimo errore commesso in termini relativi, si potra

far ricorso ad un algoritmo euristico che individui una soluzione il piu vicino

possibile a quella ottima, in un tempo accettabile. Un metodo euristico

piuttosto semplice, in grado di risolvere il problema in questione, e conosciuto

come algoritmo costruttivo.

15

2.1 Sviluppo di un algoritmo costruttivo

Il problema di taglio bidimensionale puo essere risolto sviluppando un al-

goritmo euristico costruttivo che consiste in un processo iterativo in cui si

combinano tre elementi: una lista P di pezzi ancora da tagliare (inizialmente

la lista completa dei pezzi), una lista L di rettangoli vuoti in cui un pezzo puo

essere tagliato (inizialmente contenente solo il rettangolo base R = (L, W ))

e una lista C contenente i blocchi (ovvero l’unione di uno o piu pezzi) gia

tagliati ed inizialmente vuota. Ad ogni passo un rettangolo viene scelto da

L e vi si inserisce un pezzo scelto da P che possa esservi contenuto. Questo

produce, di solito, la rimozione del rettangolo di partenza e la creazione di

nuovi rettangoli che si aggiungono a L. Il processo termina quando L = ∅oppure nessuno dei pezzi rimanenti riesce a entrare in uno dei rettangoli di

L. L’algoritmo puo essere cosı sintetizzato:

1. Inizializza L, P e C.

2. Se ∃ un pezzo che entra in uno dei rettangoli, prosegui, se no STOP.

3. Scegli un rettangolo di L.

4. Scegli un pezzo di P da tagliare.

5. Aggiorna L, P e C e ritorna al punto 2.

2.1.1 Inizializzazione di P , L e C

Come gia detto in precedenza, inizialmente, la lista dei rettangoli vuoti Lcontiene solo il rettangolo base, la lista dei blocchi C e vuota, mentre la lista

dei pezzi P contiene l’elenco completo dei pezzi da tagliare. Quest’ultima

deve essere ordinata per favorire l’applicazione dell’algoritmo costruttivo,

seguendo tre criteri:

1. Pezzi ordinati per valori Pi∗ li∗wi decrescenti, in modo da dare priorita

ai pezzi che devono per forza essere tagliati (cioe quelli con Pi > 0).

2. Se Pi = 0 ∀i, i pezzi devono essere ordinati per valori ei decrescenti.

16

3. Se ei = 1 ∀i, i pezzi devono essere ordinati per valori li ∗wi decrescenti.

2.1.2 Scelta del rettangolo

La scelta migliore e quella di selezionare il rettangolo R∗ ∈ L con l’area

minore in modo da poter soddisfare le richieste dei pezzi piu piccoli coi ret-

tangoli piu piccoli, lasciando i rettangoli piu grandi per i pezzi piu grandi.

Se scegliessimo un rettangolo grande all’inizio e lo usassimo per tagliare un

pezzo piccolo, i nuovi rettangoli creati dall’inserimento del pezzo potrebbero

risultare inutili per i pezzi piu grandi ancora da tagliare.

2.1.3 Scelta del pezzo da tagliare

Scelta di un pezzo pi e di una quantita ni ≤ Qi in modo da formare

il blocco b∗

Una volta selezionato il rettangolo R∗, deve essere scelto il primo pezzo pi di

P (ordinato come specificato in precedenza) che entra in R∗.

Se il pezzo selezionato per essere tagliato ha un upper bound Qi > 1, puo

essere presa in considerazione l’idea di tagliare un blocco, ovvero diverse copie

del pezzo in questione disposte in righe e colonne, purche il numero di questi

pezzi non superi Qi e il blocco continui ad avere una forma rettangolare. La

Figura 3 mostra esempi di questa situazione.

Figura 3 Varie scelte sulla composizione dei blocchi

17

Due criteri sono stati considerati per la selezione del pezzo:

1. Il primo pezzo nella lista ordinata P .

Questo e il modo piu ragionevole di scegliere il pezzo, in accordo con

quanto detto prima.

2. Il pezzo che produce il maggiore incremento nella funzione obiettivo.

Questo e un modo di agire molto piu greedy.

Posizionamento del blocco nel rettangolo R∗

Poiche con molta probabilita il blocco b∗ non riempira completamente il ret-

tangolo R∗, tale blocco deve essere posizionato in uno degli angoli del rettan-

golo, e piu precisamente nell’angolo che dista di meno da uno degli angoli del

rettangolo di base. Posizionando i blocchi il piu vicino possibile agli angoli del

rettangolo base, si concentrano tutti i rettangoli vuoti al centro, facilitando

cosı l’operazione di ”fusione” e recuperando spazio per i pezzi successivi.

2.1.4 Aggiornamento di P , L e C

La lista C dei blocchi deve essere aggiornata con l’indice i del pezzo inserito

con il relativo numero di pezzi ni che formano il blocco. Inoltre sara necessa-

rio indicare in qualche modo la posizione del blocco all’interno del rettangolo

selezionato.

La lista P dei pezzi da tagliare deve essere aggiornata modificando l’upper

bound del pezzo inserito Qi = Qi−ni; se Qi = 0, il pezzo i deve essere rimosso

dalla lista.

La lista L dei rettangoli vuoti deve essere aggiornata eliminando il rettan-

golo appena utilizzato e, a meno che il blocco non occupi tutto lo spazio del

rettangolo vuoto, inserendovi i nuovi rettangoli appena formati. Cosı facen-

do pero si presenterebbero un sacco di piccoli rettangoli, impedendo a pezzi

grandi di essere tagliati, anche se lo spazio libero totale lo permetterebbe.

Occorre percio fondere questi rettangolini.

18

Fusione dei rettangoli

Quando si fondono due rettangoli, possono comparire al massimo tre nuovi

rettangoli, di solito uno piu grande e uno o due piu piccoli, come da Figura 4.

Figura 4 Fusione di due rettangoli

Tra le varie alternative di fusione, la migliore e quella che favorisce l’inse-

rimento dei pezzi che si trovano nelle prime posizioni della lista P . Seguendo

questo consiglio, le condizioni per una fusione tra due rettangoli sono le

seguenti:

1. Se l’ordine del miglior pezzo che entra nel piu grande dei nuovi ret-

tangoli e minore dell’ordine dei pezzi che entrano nei vecchi rettangoli,

deve avvenire la fusione.

2. Se l’ordine del miglior pezzo che entra nel piu grande dei nuovi rettan-

goli e maggiore dell’ordine dei pezzi che entrano nei vecchi rettangoli,

non deve avvenire la fusione.

3. Se l’ordine del miglior pezzo che entra nel piu grande dei nuovi rettan-

goli e uguale all’ordine dei pezzi che entrano nei vecchi rettangoli, la

fusione deve avvenire solo se l’area del piu grande tra i nuovi rettangoli

e maggiore dell’area di ognuno dei vecchi rettangoli.

19

2.2 Esempio di applicazione dell’algoritmo

Riprendendo l’esempio di Figura 2, proviamo ad applicare l’algoritmo co-

struttivo nel caso constrained ovvero con Pi = 0 ∀i. Denotiamo ogni rettan-

golo con la quadrupla (x1, y1, x2, y2) dove (x1, y1) sono le coordinate dell’an-

golo in basso a sinistra, mentre (x2, y2) sono le coordinate dell’angolo in alto

a destra. Il processo puo essere seguito attraverso la Figura 5.

Figura 5 Applicazione dell’algoritmo costruttivo

Inizialmente L = {R} = {(0, 0, 10, 10)} e P = {10, 9, 6, 5, 7, 2, 8, 3, 1, 4}in ordine di efficienza. Consideriamo il primo pezzo della lista, il pezzo 10,

con Q10 = 2 e proviamo a tagliare un blocco formato da due pezzi nell’an-

golo in basso a destra del primo (e unico) rettangolo di L. Aggiorniamo

P = {9, 6, 5, 7, 2, 8, 3, 1, 4} e L = {(4, 0, 10, 10)}. Ora proviamo a taglia-

re il pezzo 9 con Q9 = 3, ma solamente 2 copie possono entrare nel ret-

tangolo. Tagliamo il blocco nell’angolo in basso a destra e riaggiorniamo

P = {9, 6, 5, 7, 2, 8, 3, 1, 4} (con Q9 = 1) e L = {(4, 7, 10, 10)}. A questo

20

punto i pezzi 9, 6 e 5 non entrano nel rettangolo e quindi viene selezionato

il pezzo 7 con Q7 = 2. Il blocco, formato da due pezzi, viene posizionato

nell’angolo in alto a destra. Aggiorniamo ancora P = {9, 6, 5, 2, 8, 3, 1, 4} e

L = {(4, 7, 6, 10), (6, 7, 10, 8)}, ma nessun altro pezzo puo entrare in nessuno

dei rettangoli restanti. Prima di finire il processo proviamo a fondere i due

rettangoli ottenendo L = {(4, 7, 10, 8), (4, 8, 6, 10)}, ma anche in questo caso

nessun pezzo puo piu essere tagliato. Il risultato finale trovato dall’algorit-

mo costruttivo e di 242, contro il 247 che rappresenta la soluzione ottima.

Considerando il fatto che questo problema e uno di quelli considerati ”piu

facili”, il risultato puo risultare inaccettabile.

Per questo motivo l’algoritmo costruttivo si presenta inadeguato perche

cerca sempre di effettuare il passo piu conveniente in quel momento (come

un algoritmo di tipo greedy) e preclude cosı la strada verso altre soluzioni mi-

gliori. Per superare questo inconveniente e possibile sviluppare un algoritmo

che si fondi su una tecnica di tipo metaeuristico.

21

Capitolo 3

Tecniche metaeuristiche

Negli ultimi anni hanno acquisito importanza via via maggiore alcuni ap-

procci euristici che, anziche essere progettati in maniera specifica per un da-

to problema, sono di tipo generale. Tali tecniche sono dette metaeuristiche.

La struttura e l’idea di fondo di ciascuna metaeuristica sono sostanzialmente

fissate, ma la realizzazione delle varie componenti dell’algoritmo dipende dai

singoli problemi. Tra queste le piu interessanti sono:

• genetic search

• simulated annealing

• ant colony optimization

• GRASP

• tabu search

Gli approcci metaeuristici possono vedersi in realta in modo omogeneo,

come generalizzazioni di un unico approccio fondamentale, che e quello del-

la ricerca locale. La ricerca locale si basa su quello che e, per certi versi,

l’approccio piu semplice e istintivo all’ottimizzazione: andare per tentativi.

In effetti, l’idea di funzionamento della ricerca locale (come del resto quella

delle varie metaeuristiche) e talmente elementare che e sorprendente consta-

tare la loro relativa efficacia. Va tuttavia detto fin da ora che a fronte di

23

questa relativa semplicita, l’applicazione di una qualunque metaeuristica a

un problema di ottimizzazione combinatoria richiede comunque una messa

a punto accurata e talvolta laboriosa. Consideriamo un problema di mini-

mizzazione, e una sua soluzione ammissibile x, con associato il valore della

funzione obiettivo f(x). La ricerca locale consiste nel definire un intorno di

x (detto, nella terminologia della ricerca locale, vicinato), e nell’esplorarlo

in cerca di soluzioni migliori, se ve ne sono. Se, in questo vicinato di x, si

scopre una soluzione y per cui f(y) < f(x), allora ci si sposta da x a y e si

riparte da y con l’esplorazione del suo intorno. Se invece nel vicinato di x

non si scopre nessuna soluzione migliore, allora vuol dire che x e un minimo

locale.

Nella ricerca locale classica, arrivati in un minimo locale, l’algoritmo si

ferma e restituisce questo minimo come valore di output. Ovviamente, non

si ha nessuna garanzia in generale che tale valore costituisca una soluzione

ottima del problema; anzi, tipicamente esso puo essere anche molto distante

dall’ottimo globale. Le metaeuristiche, in effetti, sono state concepite proprio

per cercare di ovviare al problema di rimanere intrappolati in un minimo

locale. Vediamo dapprima piu da vicino la ricerca locale, e poi esamineremo

gli approcci metaeuristici.

3.1 Ricerca Locale

In base a quanto detto sopra, possiamo riassumere schematicamente l’algo-

ritmo generale di ricerca locale come segue. Con x indichiamo una generica

soluzione ammissibile del problema, con N(x) il suo vicinato, e con f(x) la

funzione obiettivo.

1. Scegli una soluzione iniziale x.

2. Genera le soluzioni nel vicinato N(x).

3. Se in N(x) c’e una soluzione y tale che f(y) < f(x), allora poni

x := y e vai al punto 2, altrimenti STOP.

24

Lo spostamento da x a y al punto 3 viene spesso chiamato mossa. Nella

ricerca locale, come si vede, si ha un miglioramento della funzione obiettivo

in corrispondenza di una mossa. Per applicare l’approccio della ricerca locale

a un particolare problema, bisogna fare alcune scelte di fondo.

• Occorre avere a disposizione una soluzione iniziale ammissibile. Questa

puo essere generata da un’euristica ad hoc per il particolare problema,

o addirittura puo essere generata casualmente. E’ possibile anche ese-

guire l’algoritmo a partire da diverse soluzioni iniziali, ottenendo cosı

diverse soluzioni euristiche, e scegliere poi la migliore.

• Bisogna definire in modo preciso e opportuno il vicinato di una solu-

zione.

• Definito il vicinato, occorre avere a disposizione un algoritmo efficiente

per esplorarlo. E’ inutile definire il vicinato in un modo teoricamente

potente per poi non essere in grado di esplorarlo in un tempo di calcolo

ragionevole.

A seconda di come vengono fatte queste scelte, l’algoritmo risultante puo es-

sere piu o meno efficiente, e la soluzione proposta dalla ricerca locale risultare

piu o meno buona.

Benche ogni applicazione del concetto di ricerca locale a un problema

abbia le sue peculiarita, alcuni aspetti sono abbastanza comuni a molte

realizzazioni.

• La definizione del vicinato e in genere connessa al concetto di pertur-

bazione di una soluzione ammissibile, nel senso che la ricerca avviene

tra le soluzioni che si ottengono perturbando una soluzione iniziale.

• Spesso di parla di forza di un vicinato. Un vicinato definito in un certo

modo e tanto piu forte quanto piu la qualita delle soluzioni prodotte

dall’algoritmo e indipendente dalla bonta della soluzione di partenza.

25

Per cui si puo affrontare il problema senza perdere tempo a generare

buone soluzioni iniziali. Anzi, conviene generare molte soluzioni iniziali

casualmente, sperando cosı di avere un campionamento rappresentativo

dell’intera regione ammissibile.

• Un altro aspetto caratterizzante un approccio di ricerca locale e il modo

in cui deve essere esplorato il vicinato di una soluzione. Due strategie

antitetiche sono first improvement e steepest descent. Nel primo caso

l’esplorazione del vicinato termina non appena si trova una soluzione

migliore di quella corrente. Nel secondo, invece, lo si esplora comunque

tutto cercando il massimo miglioramento che quel vicinato consente di

ottenere. In genere si preferisce il primo approccio, ma non e una regola

fissa.

Come gia osservato, un grosso pregio della ricerca locale sta nella sua

semplicita concettuale e realizzativa, pur consentendo tale metodo di otte-

nere risultati interessanti. Tuttavia, il criterio di arresto dell’algoritmo di

ricerca locale visto finora appare in generale troppo rigido. A parte alcu-

ni casi particolari (e fortunati) in cui la funzione obiettivo ha determinate

caratteristiche di convessita, nella grande maggioranza dei problemi reali

la funzione obiettivo da minimizzare presenta un grande numero di minimi

locali, talora anche molto lontani dal minimo globale.

Per ovviare a questo problema, a partire dalla meta degli anni ’80, la

ricerca si e indirizzata verso approcci euristici che generalizzano la ricerca

locale cercando di evitare i suoi principali difetti.

3.2 Genetic Search

Con il termine weak methods si indicano quei metodi di risoluzione di proble-

mi che si basano su poche assunzioni (o conoscenze) relative alle particolari

strutture e caratteristiche dei problemi stessi, motivo per il quale tali metodi

sono applicabili ad una vasta classe di problemi. Gli algoritmi genetici rien-

trano pienamente in questa categoria, dal momento che essi sono in grado

26

di compiere una efficiente ricerca anche quando tutta la conoscenza a priori

e limitata alla sola procedura di valutazione che misura la qualita di ogni

punto dello spazio di ricerca (misura data, ad esempio, dal valore della fun-

zione obiettivo). Questa caratteristica conferisce a tali algoritmi una grande

robustezza, ovvero una grande versatilita che li rende applicabili a diversi

problemi, al contrario dei metodi convenzionali che, in genere, non trovano

altra applicazione che quella relativa al problema per cui sono stati ideati.

Gli algoritmi genetici integrano l’abilita di semplici rappresentazioni nel

codificare strutture molto complicate con la potenza esibita da semplici tra-

sformazioni (che agiscono su tali rappresentazioni) nel migliorare queste strut-

ture. Questo miglioramento e l’obiettivo primario dell’ottimizzazione. Infat-

ti, sotto l’azione di certe trasformazioni genetiche, si registrano miglioramenti

o, se si vuole, evoluzioni delle rappresentazioni in modo da imitare il processo

di evoluzione di popolazioni di organismi viventi. In natura, il problema che

ciascuna specie deve affrontare e quello della sopravvivenza, che coincide con

il trovare un adattamento vantaggioso ad un ambiente mutevole e talvolta

ostile.

Le trasformazioni genetiche che consentono una evoluzione in tal senso

sono quelle che alterano il patrimonio genetico della specie contenuto nei cro-

mosomi di ciascun individuo: esse sono il crossover di gruppi di geni parentali

e la mutazione. I cromosomi codificano strutture biologiche molto compli-

cate, mentre le trasformazioni genetiche costituiscono il mezzo attraverso il

quale si generano nuove strutture biologiche piu evolute delle precedenti.

L’idea di fondo degli algoritmi genetici e la seguente. Supponiamo di

avere un certo insieme di soluzioni di un problema di ottimizzazione. Tra

queste, ce ne saranno di migliori e di peggiori. La qualita di una soluzione

e misurata da una funzione di merito, detta fitness function, che in genere

coincidera con la funzione obiettivo, ma non sempre. A questo punto, voglia-

mo generare nuove soluzioni, con la speranza ovviamente che fra queste ve ne

siano sempre di migliori. L’idea e allora quella di ”accoppiare” le soluzioni

tra di loro in modo che ”diano alla luce” nuove soluzioni. Allora, da una cer-

ta popolazione di individui (soluzioni), se ne ricava un’altra, che costituisce

27

una nuova generazione, ossia quella dei figli della popolazione di partenza.

Questa nuova generazione potrebbe essere molto piu numerosa della prece-

dente, e allora quello che si fa e effettuare una selezione, cioe si escludono

dalla popolazione tutte le soluzioni che hanno un valore di fitness function

inferiore a una certa soglia.

Con la popolazione cosı selezionata, si ricomincia, generando quindi i

nipoti delle soluzioni di partenza e cosı via, per un numero prefissato di

generazioni. Come si vede, in questo contesto cosı generale vi sono diverse

scelte da fare. Anzitutto, negli algoritmi genetici gioca un ruolo fondamentale

il modo in cui vengono rappresentate le soluzioni ammissibili. Tipicamente,

una soluzione sara rappresentata da un insieme di stringhe di interi o binarie,

che sono i cromosomi. Ciascun cromosoma a sua volta e composto da geni

(i singoli bit). Dalla combinazione di due individui (crossover) nascera una

nuova soluzione del problema. I geni nei cromosomi del nuovo individuo

dovranno ovviamente provenire da quelli dei due genitori.

Un semplice modo di realizzare questo e il seguente. Se indichiamo con xi

e yi l’i-esimo gene di un certo cromosoma dei due genitori, il corrispondente

gene nel cromosoma-figlio puo essere posto rispettivamente uguale all’uno o

all’altro con probabilita p e 1 − p. Si noti che in questo modo si introduce

un elemento probabilistico nell’algoritmo. Peraltro, reiterando piu volte il

processo aleatorio di crossover, diverse soluzioni possono essere ottenute dalla

stessa coppia di genitori. E’ evidente che la scelta dell’operatore di crossover

e particolarmente importante; tuttavia non c’e solo questa. Un’altra scelta

molto importante e quella relativa alla dimensione della popolazione: a ogni

generazione, infatti, verranno escluse dall’evoluzione dell’algoritmo tutte le

soluzioni con basso valore di funzione obiettivo. E’ una scelta comune quella

di mantenere costante nei vari passi il numero di individui della popolazione.

E’ implicito, in quanto detto finora, che l’efficacia degli algoritmi genetici

risiede nel fatto empirico che se si combinano soluzioni buone, si ottengono

soluzioni ancora buone. Tuttavia, sperimentalmente, si e potuto osservare che

anche gli algoritmi genetici, dopo un certo numero di iterazioni, tendono a

produrre soluzioni che non migliorano piu. Per diversificare la ricerca, allora,

28

si introduce un nuovo operatore, detto mutazione. Prima cioe di procedere

alla successiva generazione, in alcuni degli individui della popolazione viene

alterato un gene. Ovviamente, gli operatori di mutazione dipendono molto

dal particolare problema in esame.

Da quanto detto, un algoritmo genetico e fondamentalmente caratteriz-

zato da cinque componenti:

1. una rappresentazione delle soluzioni generalmente sotto forma di strin-

ghe di bit;

2. un criterio per generare la popolazione iniziale di soluzioni;

3. una funzione di valutazione che misura la qualita di ciascuna soluzione;

4. un insieme di operatori genetici che trasformano punti-soluzione in altri

punti;

5. un insieme di valori per i parametri di controllo dell’algoritmo.

Lo schema di principio di un algoritmo genetico e quello che segue:

1. Genera la popolazione iniziale.

2. Seleziona i genitori.

3. Genera i figli.

4. Esegui il ricambio generazionale.

5. Se condizione d’uscita, STOP, altrimenti torna al punto 2.

L’algoritmo inizia con la creazione di una popolazione iniziale di soluzio-

ni, generata di solito in modo casuale. Quindi prosegue con un ciclo la

cui condizione d’uscita e determinata o dal raggiungimento di una soluzio-

ne soddisfacente, vale a dire caratterizzata da un valore di fitness giudicato

buono, oppure dal raggiungimento di un numero prefissato di generazioni da

elaborare.

Il ciclo che realizza la generica iterazione di un algoritmo genetico deter-

mina il passaggio da una generazione ad un’altra. Esso inizia con la selezione

29

dei genitori dalla popolazione corrente, ovvero degli individui che parteci-

pano alla fase di riproduzione. Una possibile implementazione del test di

sopravvivenza consiste nell’assegnare a ciascun esemplare una probabilita di

figurare nella lista dei genitori proporzionale al proprio valore di fitness. In

questo modo, si seleziona il patrimonio genetico migliore di tutta la popo-

lazione perche esso venga trasmesso alle generazioni successive. Alla fase di

selezione dei genitori segue quella di riproduzione, mediante la quale avvie-

ne la generazione di nuovi punti dello spazio di ricerca: ciascuna coppia di

individui prescelti genera, con probabilita pc (probabilita di crossover), una

coppia di figli.

Il processo di riproduzione simula il meccanismo biologico di crossover in

cui si verifica uno scambio di gruppi di geni appartenenti ai due genitori. Nel

caso piu semplice viene selezionato casualmente un punto di rottura del cro-

mosoma e quindi, in corrispondenza di questo taglio, si ha uno scambio delle

parti omologhe. Tuttavia si possono adottare tecniche piu sofisticate. L’al-

tro operatore genetico responsabile di variazioni nei parametri codificati dal

cromosoma e la mutazione, che consiste nell’effettuare, con una probabilita

pm, una complementazione del valore assunto da un bit in corrispondenza ad

una posizione scelta in modo casuale. Questo operatore interviene sui nuovi

nati.

La parte finale del ciclo comporta la fase di ricambio generazionale du-

rante la quale si deve formare la popolazione da trasmettere alla generazione

successiva, operando una selezione fra gli individui di cui si dispone (genitori

e figli). Questa fase e necessaria per limitare la dimensione della popolazio-

ne di soluzioni che, altrimenti, crescerebbe esponenzialmente con l’evolvere

dell’algoritmo. Esistono in merito diverse soluzioni. La prima costituisce il

metodo di ricambio generazionale classico in cui i genitori lasciano il posto

ai nuovi nati: alla popolazione dei genitori si sostituisce la popolazione dei

figli. La seconda si basa ancora sul ricambio classico ma con l’accorgimen-

to di preservare dalla scomparsa l’esemplare migliore in assoluto, qualora la

nuova popolazione non dovesse contenere nessuna soluzione migliore di esso.

La terza soluzione prevede una selezione naturale tra genitori e figli in base

30

alla qualita di ciascuno di essi, ossia sopravvivono solo gli esemplari migliori.

Un ottimo algoritmo che risolve il problema di taglio bidimensionale con

la tecnica del genetic search e quello realizzato da Beasley [15]. Questo

algoritmo si fonda su una formulazione non lineare del problema.

3.3 Simulated Annealing

Il simulated annealing e una tecnica stocastica che si ispira alla meccanica

statistica per trovare soluzioni per problemi di ottimizzazione sia continua

che discreta. Ricercare il minimo globale di una funzione di costo con molti

gradi di liberta e un problema molto complesso, se questa funzione ammette

un gran numero di minimi locali. Uno degli obiettivi principali dell’ottimizza-

zione e proprio quello di evitare di rimanere intrappolati in un minimo locale.

Questo e uno dei limiti piu grandi delle tecniche di ricerca locale. I metodi

basati sul simulated annealing applicano un meccanismo probabilistico che

consente alla procedura di ricerca di fuggire da questi minimi locali. L’idea

e quella di accettare, in certi casi, oltre alle transizioni che corrispondono a

miglioramenti nella funzione obiettivo, anche quelle transizioni che portano

a peggioramenti nel valore di questa funzione di valutazione. La probabilita

di accettare tali deterioramenti varia nel corso del processo di ricerca, e di-

scende lentamente verso zero. Verso la fine della ricerca, quando vengono

accettati solo miglioramenti, questo metodo diventa una semplice ricerca lo-

cale. Tuttavia, la possibilita di transire in punti dello spazio di ricerca che

deteriorano la soluzione ottima corrente, consente di abbandonare i minimi

locali ed esplorare meglio l’insieme delle soluzioni ammissibili.

La strategia che e alla base del simulated annealing si ispira al processo

fisico di annealing (raffreddamento). Se il sistema si trova all’equilibrio ter-

mico ad una data temperatura T , allora la probabilita nT (s) che esso sia in

una data configurazione s dipende dall’energia dello stato in questione E(s),

e segue la distribuzione di Boltzmann:

nT (s) =e−E(s)/kT∑w e−E(w)/kT

(3.1)

31

dove k e la costante di Boltzmann e la sommatoria si estende a tutti gli

stati possibili w. Fu per primo Metropolis nel 1953 a proporre un metodo

per calcolare la distribuzione di un sistema di particelle all’equilibrio termico

usando un metodo di simulazione al computer. In questo metodo, supposto

che il sistema si trovi in una configurazione q avente energia E(q), si genera

un nuovo stato r, avente energia E(r), spostando una delle particelle dalla sua

posizione: la nuova configurazione viene quindi confrontata con la vecchia.

Se E(r) ≤ E(q) il nuovo stato viene accettato; se E(r) > E(q) esso non viene

respinto, bensı viene accettato con una probabilita pari a:

e−(E(r)−E(q))/kT (3.2)

Secondo questo metodo, esiste dunque una probabilita non nulla di raggiun-

gere stati di piu alta energia, ossia di scavalcare le barriere di energia che

separano i minimi globali dai minimi locali. Si noti che la funzione esponen-

ziale esprime il rapporto tra la probabilita di trovarsi nella configurazione r

e la probabilita di trovarsi in q.

Kirkpatrick utilizzo lo schema del simulated annealing per problemi di

ottimizzazione combinatoria. Per fare cio, sostituı l’energia con una funzione

costo e gli stati di un sistema fisico di particelle con le soluzioni di un pro-

blema di minimizzazione. La perturbazione delle particelle con le soluzioni

diventa allora equivalente ad uno spostamento nello spazio di ricerca verso

un punto che si trova nelle vicinanze della soluzione corrente. La minimiz-

zazione e raggiunta riscaldando dapprima il sistema, e poi raffreddandolo

lentamente fino al raggiungimento della situazione di cristallizzazione in uno

stato stabile. E’ da sottolineare il fatto che per raggiungere configurazioni

di bassa energia non e sufficiente abbassare semplicemente la temperatura; e

necessario, viceversa, attendere per ciascuna temperatura di transizione, un

tempo sufficiente a raggiungere l’equilibrio termico del sistema. Altrimenti,

la probabilita di raggiungere stati di bassa energia e sensibilmente ridotta.

La simulazione del processo di annealing, applicato a problemi di ottimiz-

zazione, richiede diversi passi preparatori. Per prima cosa si devono identifi-

care nel problema di ottimizzazione le analogie con i concetti fisici: l’energia

32

diventa la funzione di costo, le configurazioni di particelle divengono le con-

figurazioni di parametri (variabili decisionali) del problema da ottimizzare,

ricercare uno stato di minima energia significa ricercare una soluzione che

minimizza la funzione di costo, la temperatura diventa un parametro di con-

trollo. Quindi, si deve scegliere un opportuno schema di annealing (cooling

schedule), consistente nella regolazione dei parametri da cui dipende il pro-

cesso di ottimizzazione: si tratta cioe di stabilire la legge di decadimento

della temperatura e la durata del tempo necessario per il raggiungimento

dell’equilibrio termico a ciascuna temperatura. Infine si deve introdurre un

metodo di perturbazione del sistema che consenta di esplorare lo spazio di

ricerca generando nuove configurazioni.

L’algoritmo di annealing descritto da Kirkpatrick consiste nell’esecuzione

ripetuta dell’algoritmo di Metropolis per ciascuna temperatura di transizio-

ne, fino al raggiungimento della configurazione ottima del sistema, ottenuta

in prossimita della temperatura di cristallizzazione prefissata. L’algoritmo

di Metropolis, che accetta configurazioni che incrementano il costo, fornisce

il meccanismo probabilistico che consente di evitare l’intrappolamento nei

minimi locali. Lo schema di principio di un algoritmo basato sul Simulated

Annealing e riportato di seguito.

1. Inizializza C e X.

2. Poni i := 1.

3. Genera Y da X.

4. Se f(Y ) ≤ f(X), poni X := Y .

5. Altrimenti se e−(f(Y )−f(X))/C > random(0, 1), poni X := Y .

6. Incrementa i di 1.

7. Se i == L prosegui, se no torna al punto 3.

8. Riduci il parametro C.

9. Se condizione d’uscita, STOP, altrimenti torna al punto 2.

L’algoritmo parte con l’inizializzazione di X, che rappresenta una possibi-

33

le soluzione, e di C che costituisce il parametro di controllo dell’algoritmo

stesso (equivale alla temperatura nel processo di annealing). Quindi si entra

nel ciclo while da cui si esce solo quando risultera vera la condizione di arresto:

questa situazione si verifica quando si raggiunge una soluzione soddisfacente

o in corrispondenza del raggiungimento della temperatura di congelamento

prefissata per il sistema. Le fasi del ciclo while sono essenzialmente due; nella

prima si entra in un ciclo for, durante la seconda si provvede a diminuire la

temperatura del sistema secondo una legge prefissata che definisce lo sche-

ma di annealing. La prima fase corrisponde all’esecuzione dell’algoritmo di

Metropolis e viene ripetuta L volte (questo e un altro parametro essenzia-

le dello schema di annealing) per il raggiungimento dell’equilibrio termico;

la seconda fase determina la transizione da una temperatura ad un’altra.

Per quanto riguarda il problema della definizione dello schema di annealing,

ovvero della definizione dei parametri di funzionamento, esistono in merito

diverse soluzioni e una gran quantita di studi sono stati dedicati a questo

problema.

Per risolvere il problema di taglio bidimensionale, sono stati sviluppati

alcuni algoritmi euristici che utilizzano una tecnica mista, fondendo simulated

annealing e genetic search, tra questi meritano di essere citati quelli di Lai e

Chan [11], [12] e di Leung et al [13], [14]. In questi algoritmi ogni soluzione

e data da una lista ordinata di pezzi, la quale e trasformata in una sequenza

di tagli da un placement algorithm. Questo algoritmo puo essere, a seconda

dei casi, un bottom-left algorithm o un difference algorithm.

3.4 Ant Colony Optimization

La ant colony optimization (ACO) e una tecnica metaeuristica per problemi

di ottimizzazione, ispirata al sistema di comunicazione delle formiche biolo-

giche. Alcuni ricercatori hanno studiato, in laboratorio, il comportamento

di una colonia di formiche alla quale era dato accesso ad una fonte di ci-

bo (food), partendo dal loro nido (nest), attraverso due percorsi di diversa

lunghezza (Figura 6).

34

Figura 6 Esperimento con vere formiche

Inizialmente le formiche, giunte al bivio, si dividono equamente tra le

due strade, mostrando quindi di fare questa scelta in modo casuale. Dopo

un periodo di transizione di alcuni minuti pero, la maggior parte di esse

scegliera sempre la via piu breve verso il cibo. E’ stato osservato inoltre

che, a regime, la probabilita di scegliere la strada piu breve aumenta con

l’aumentare della differenza di lunghezza tra i due percorsi.

Dorigo ha costruito un modello della funzione di probabilita con la quale

avviene la scelta, al crescere del numero di formiche gia passate. Questo

fenomeno e stato spiegato supponendo che le formiche adottino un sistema

di comunicazione indiretto basato su feromone, una sostanza odorosa che

esse rilasciano sul terreno durante la marcia. Quando si trovano ad un bivio

esse effettuano una scelta probabilistica della strada da prendere, assegnando

una maggiore probabilita alla via con maggiore deposito di feromone. Questo

porta ad un effetto autocatalizzante, per cui le strade piu odorose verranno

scelte con maggiore probabilita, ottenendo sempre piu feromone e diventando

quindi sempre piu attrattive.

Immaginiamo ora due formiche che partano allo stesso momento dal nido

in una condizione di assenza di feromone sul percorso, e supponiamo anche

che prendano due strade diverse al bivio. Quella che ha avuto la fortuna

di scegliere la piu breve fara ritorno al nido per prima, e quindi potra ag-

35

giungere un secondo deposito di feromone sul suo percorso prima dell’altra.

Le successive formiche, quindi, saranno attratte dalla maggiore quantita di

feromone del percorso effettivamente piu breve e, con maggiore probabilita,

sceglieranno quello. Lo stesso avviene se si considera il viaggio di ritorno, dal

cibo verso il nido: raggiunto il bivio la scelta cadra con maggiore probabilita

sulla via che porta al nido, in quanto e stata percorsa da tutte le formiche

indiscriminatamente.

Questo semplice esempio potrebbe suggerire che, in presenza di feromone

al bivio, una scelta deterministica sarebbe migliore, in quanto la soluzione

ottima corrisponde sempre alla via piu odorosa, fin dal passaggio della secon-

da formica. Purtroppo questo e vero solo se non siamo in presenza di minimi

locali. Se invece la strada fosse costituita da un gran numero di passaggi

intermedi, ed incroci con piu di due direzioni possibili, si corre il rischio di

fermarsi a soluzioni solo parzialmente ottime. Il giusto approccio invece e

quello di utilizzare un gran numero di formiche in successione, guidate nella

loro scelta da un algoritmo probabilistico, in modo che vengano esplorate

anche soluzioni che apparentemente sono svantaggiose, ma che globalmente

potrebbero rivelarsi quelle ottime.

Questi concetti possono essere utilizzati nella soluzione di problemi di

ottimizzazione, costruendo una colonia di formiche artificiali che, simulando

l’attivita di ricerca del cibo, ottengano la soluzione ottima al problema. Que-

ste formiche artificiali altro non saranno che degli agenti software, ognuno dei

quali costruira una soluzione parziale del problema (esattamente come ogni

vera formica trova una soluzione parziale al problema del raggiungimento del

cibo), basandosi sull’informazione lasciata dagli agenti che hanno affrontato

il problema in precedenza. Le informazioni generate da ogni agente, durante

il suo cammino di ricerca di una soluzione, sono codificate in depositi di fe-

romone artificiale associato ad ogni percorso attraversato. Questo feromone

permane anche dopo il passaggio dell’agente che lo ha depositato, e verra

percepito da quelli successivi. Lo schema di una procedura che utilizzi l’ant

colony optimization, puo essere rappresentato cosı:

36

1. Inizializza i valori dei feromoni τi.

2. Costruisci una soluzione sa per ogni formica a, utilizzando una tecni-

ca euristica η e tenendo in considerazione il valore del feromeone τ ,

per un dato percorso.

3. Aggiorna i valori dei feromoni τi.

4. Se condizione di termine, STOP, altrimenti torna al punto 2.

Oltre all’attivita dei singoli agenti e possibile avere anche alcune fun-

zionalita esterne: l’evaporazione del feromone e le operazioni centralizzate.

L’evaporazione del feromone e un processo che fa diminuire il valore del de-

posito con il passare del tempo. Questo e necessario per evitare che il sistema

converga troppo in fretta verso soluzioni che non sono ottime, non dando il

tempo agli agenti di esplorare altre soluzioni. Per operazioni centralizzate

si intendono tutti quegli interventi che non sono messi in atto direttamente

dagli agenti, come per esempio l’aggiunta di un deposito extra di feromone

su un particolare percorso che, in un certo momento, sembra essere promet-

tente, oppure la modifica di alcuni parametri di funzionamento degli agenti

in particolari condizioni.

Un problema chiave nella progettazione di algoritmi di tipo ACO e il

bilanciamento tra esplorazione e sfruttamento. Quando gli agenti si muovono

principalmente alla ricerca di zone gia esplorate, quindi con elevati depositi

di feromone, essi sfruttano le esperienze degli agenti precedenti, cercando di

ottenere il miglior risultato finale. Al contrario un movimento principalmente

casuale porterebbe all’esplorazione di nuove zone. Se gli agenti si muovessero

solo in funzione del feromone, dopo poco tutti percorrerebbero sempre la

stessa strada, portandosi ad una condizione di stagnazione. D’altra parte

un comportamento troppo casuale porterebbe alla dispersione del feromone,

rallentando o impedendo la messa in evidenza di una soluzione ottima.

Per ottenere il giusto equilibrio tra questi comportamenti, normalmente

si agisce sul deposito di feromone e sull’influenza che esso ha sugli agenti.

La sua distribuzione infatti determina la probabilita che una soluzione sia

37

visitata o meno. Una tecnica molto usata per evitare l’eccessivo sfruttamento

e quella di fare in modo che la quantita di feromone che un agente deposita sia

proporzionale alla bonta del percorso effettuato. In questo modo si permette

alle nuove strade, se sono migliori delle vecchie, di emergere piu velocemente.

Un algoritmo di tipo ACO che risolve il problema di taglio, e stato

sviluppato da Ducatelle e Levine [26].

3.5 GRASP

La tecnica GRASP (Greedy Randomized Adaptive Search Procedure) e stata

sviluppata da Feo e Resende [16] per risolvere Set Covering Problems ed e

una procedura iterativa in cui ogni iterazione e caratterizzata da due fasi:

• fase costruttiva;

• fase di miglioramento (ricerca locale).

La soluzione migliore trovata durante tutte le iterazioni viene mantenuta co-

me risultato della procedura. Lo pseudocodice della procedura puo essere

rappresentato cosı:

1. Carica i dati iniziali del problema.

2. Inizializza la miglior soluzione trovata (BSF).

3. Poni S = ConstructivePhase().

4. Poni S ′ = ImprovementPhase(S).

5. Se S ′ > SBF poni SBF = S ′.

6. Se condizione di termine, STOP, altrimenti torna al punto 3.

Le condizioni di termine possono essere di diversa natura; la procedura puo

ad esempio terminare qualora sia stato raggiunto il numero massimo di ite-

razioni consentite, oppure sia stata trovata una soluzione il cui valore della

funzione obbiettivo sia uguale all’upper bound calcolato.

38

Durante la ”fase costruttiva” della procedura, viene costruita una soluzio-

ne ammissibile in maniera iterativa aggiungendo un elemento alla volta; ad

ogni iterazione di questa fase la scelta dell’elemento che deve essere aggiun-

to e determinata ordinando, rispetto ad una funzione di tipo greedy, tutti

gli elementi in una lista di ”candidati”. Questa funzione misura il benefi-

cio (miopico) associato alla scelta di ciascun elemento. L’euristica e defini-

ta come adaptive (adattabile) perche i benefici associati ad ogni elemento

sono aggiornati ad ogni iterazione della fase di costruzione per riflettere i

cambiamenti avvenuti in seguito alla selezione dell’elemento nell’iterazione

precedente. La componente probabilistica della procedura si manifesta nella

scelta dell’elemento che ad ogni iterazione deve essere aggiunto per formare

la soluzione; esso e infatti scelto casualmente (randomized) tra i migliori can-

didati nella lista e non e detto quindi che sia il migliore in assoluto. La lista

contenente i migliori ”candidati” che possono essere scelti viene indicata col

termine RCL (Restricted Candidate List) e puo essere fissa oppure variare ad

ogni iterazione seguendo l’andamento di un parametro δ. Questa tecnica di

scelta permette dunque di ottenere soluzioni diverse ad ogni iterazione della

procedura GRASP.

La ”fase di miglioramento”, invece, parte dalla soluzione trovata durante

la fase costruttiva e, attraverso una ricerca locale, esplora il vicinato finche

non viene raggiunto un punto di minimo locale.

Per quanto riguarda la risoluzione del problema di taglio bidimensionale

con l’utilizzo di una tecnica GRASP e da menzionare l’algoritmo realizzato

da Alvarez-Valdes et al [1].

3.6 Tabu Search

Nella ricerca locale classica, ogniqualvolta si esplora il vicinato di una so-

luzione, l’unica informazione relativa alla storia dell’algoritmo fino a quel

momento e la migliore soluzione corrente e il corrispondente valore della

funzione obiettivo.

L’idea della tecnica tabu search e invece quella di mantenere una me-

39

moria di alcune informazioni sulle ultime soluzioni visitate, orientando la

ricerca in modo tale da permettere di uscire da eventuali minimi locali. Piu

precisamente, vedremo che la struttura del vicinato di una soluzione varia

da iterazione a iterazione. Come in tutti gli algoritmi di ricerca locale, se,

nell’esplorazione del vicinato N(x) di una soluzione x, si scopre una solu-

zione migliore dell’ottimo corrente, si ha una transizione su quella soluzione

e si inizia a esplorare quel vicinato. Tuttavia, se la soluzione corrente e un

minimo locale, l’algoritmo di ricerca locale si fermerebbe. E’ chiaro allora

che, per poter sfuggire ai minimi locali, e necessario accettare mosse che pos-

sano portare a un peggioramento della soluzione corrente, pur di spostarsi

sufficientemente lontano dal minimo locale.

Supponendo allora di trovarci in un minimo locale x, dobbiamo anzitutto

scegliere su quale soluzione spostarci, dal momento che nessuna, in N(x),

sara migliore di x. Negli algoritmi di tabu search si fa in genere la scelta di

spostarsi sulla soluzione y ∈ N(x) per la quale e minimo il peggioramento

della funzione obiettivo. A questo punto pero nasce il problema che sta alla

base dell’idea di tabu search. Se ci portiamo sulla soluzione y, ed esploriamo il

suo vicinato, e assai probabile che la migliore soluzione in N(y) risulti essere

proprio x, cioe quella da cui vogliamo viceversa sfuggire. Piu in generale,

dal momento che stiamo permettendo anche mosse che non migliorano la

funzione obiettivo, puo sempre presentarsi il pericolo, anche dopo un certo

numero di mosse, di tornare su una soluzione gia visitata. Ecco allora che

entra in gioco l’uso della memoria.

L’idea e quella di utilizzare le informazioni sulle ultime mosse effettuate

per evitare il pericolo di ciclaggio. Precisamente, mantenendo memoria delle

ultime mosse che hanno portato alla soluzione attuale, sara possibile proibire

quelle mosse per un certo tempo al fine appunto di prevenire ricadute in punti

gia visitati. Dunque, mentre nella ricerca locale si parla di vicinato N(x) di

una soluzione x, nella tabu search possono essere escluse, dall’esplorazione

di N(x), alcune soluzioni, a seconda dell’itinerario seguito per giungere in x.

Diciamo allora che l’insieme delle soluzioni da esplorare dipende, oltre che

dalla soluzione x, anche dall’iterazione k del metodo, ossia il vicinato diviene

40

N(x, k). Per realizzare questo concetto, si fa uso di uno strumento chiamato

tabu list T .

La tabu list e una coda in cui vengono memorizzate le ultime |T | mosse

effettuate. Le mosse opposte a queste, che potrebbero dunque avere l’effet-

to di riavvicinarsi a soluzioni gia visitate, vengono proibite nella situazione

attuale, cioe vengono escluse dal vicinato della soluzione corrente tutte le

soluzioni ottenibili per mezzo di una mossa tabu. Si noti che si potrebbe

pensare di memorizzare direttamente le ultime |T | soluzioni, ma questo puo

essere estremamente poco pratico per problemi di grandi dimensioni. Si pre-

ferisce allora memorizzare direttamente le mosse. La tabu list e una coda,

nel senso che, a ogni passo, la mossa che era nella tabu list da maggior tempo

viene cancellata, e torna a essere quindi ammessa. Anche se il ruolo della

tabu list dovrebbe essere abbastanza chiaro, non e in generale ovvio stabilire

quale debba essere la lunghezza piu appropriata per la tabu list. Infatti,

una tabu list troppo lunga potrebbe inutilmente vincolare il processo di ri-

cerca anche quando la distanza percorsa da una determinata soluzione e gia

sufficientemente elevata da rendere improbabile un ritorno su tale soluzione.

D’altro canto, una tabu list troppo corta puo presentare invece il problema

opposto, cioe potrebbe rendere possibile il ciclaggio. Il valore ottimale della

lunghezza della tabu list varia tipicamente da problema a problema anzi,

talora addirittura da istanza a istanza, ma comunque raramente eccede il

valore dieci.

Rispetto a un algoritmo di ricerca locale, va definito il criterio di arresto,

dal momento che esso non e piu dato dall’evento per cui la funzione obiettivo

non migliora. In pratica si utilizzano i seguenti criteri di arresto:

• il numero di iterazioni raggiunge un valore kmax prefissato;

• il numero di iterazioni dall’ultimo miglioramento della funzione obiet-

tivo raggiunge un valore k′max prefissato;

• e possibile certificare che l’ottimo corrente e l’ottimo globale.

Un ultimo ingrediente di un algoritmo di tabu search nasce dalla seguen-

41

te osservazione. A un generico passo dell’algoritmo, si hanno un insieme di

mosse tabu. Puo pero accadere che la soluzione in cui ci si porterebbe appli-

cando una di queste mosse tabu abbia delle caratteristiche che la rendono in

qualche modo interessante. In tal caso si esegue la mossa, nonostante fosse

nella tabu list. Tali caratteristiche vengono chiamate criteri di aspirazio-

ne. Quello di gran lunga piu utilizzato e quello per cui una mossa tabu puo

essere forzata (overruled) se la soluzione cui essa da luogo ha un valore di

funzione obiettivo migliore dell’ottimo corrente (dunque tra l’altro si tratta

certamente di una soluzione mai visitata).

Un approccio di tabu search puo comprendere molti altri ”accessori”, qua-

li le tabu list di lunghezza variabile nel corso dell’algoritmo, nonche strategie

di diversificazione e di intensificazione della ricerca.

A questo punto possiamo riassumere la struttura di un algoritmo di tabu

search (che come e evidente generalizza la ricerca locale):

1. Scegli una soluzione iniziale x; k := 0 e inizializza la tabu list T = ∅.2. Incrementa il contatore k e genera le soluzioni nel vicinato N(x, k).

3. Trova la soluzione y per cui f(y) (o un’altra funzione f (y)) e minima

con y ∈ N(x, k), y 6= x e che non viola nessun tabu oppure che soddi-

fa un criterio di aspirazione.

4. Se f(y) < f(x), poni x∗ := y.

5. Aggiorna T inserendo la mossa che fa passare da x a y e poni x := y.

6. Se condizione d’uscita, STOP, altrimenti torna al punto 2.

La tabu search e applicabile, in linea di principio, pressoche a qualsiasi

problema di ottimizzazione combinatoria. Tuttavia, alla scelta progettuale

relativa al vicinato (come del resto gia nel caso della ricerca locale) va aggiun-

ta ora quella relativa alla definizione della mossa tabu e alla lunghezza della

tabu list. L’esperienza computazionale mostra che l’elemento piu importante

nell’efficienza di un algoritmo di tabu search e la scelta del vicinato. Una

caratteristica importante di un vicinato e la raggiungibilita della soluzione

42

ottima. Ci si chiede cioe se, data una qualunque soluzione iniziale, esiste

un cammino di soluzioni che, passando da un vicinato all’altro, consente di

raggiungere l’ottimo globale. Non si richiede che l’algoritmo sia in grado ef-

fettivamente di trovarlo (questo dipendera da come vengono scelte le mosse

tabu, dal criterio di arresto etc.), ma solo che con una certa definizione di

vicinato questo sia possibile, almeno in teoria.

Un altro aspetto molto rilevante e la scelta della funzione obiettivo. La

scelta piu naturale ovviamente e quella di usare nell’algoritmo di tabu search

la funzione che si desidera minimizzare. Questa scelta pero non necessa-

riamente e quella piu conveniente. In molti problemi, tipicamente il valore

ottimo e un numero molto basso, e comunque non molto distante dal valore

della soluzione iniziale, se questa e prodotta con una qualsiasi delle euristiche

rapide presenti in letteratura. La conseguenza e che un numero enorme di

soluzioni ammissibili hanno in realta lo stesso valore, e dunque la funzione

obiettivo assai raramente avra dei miglioramenti nel corso dell’algoritmo. La

conseguenza e che l’algoritmo seguira un cammino abbastanza casuale nella

regione ammissibile, dal momento che un numero elevatissimo di direzioni

di ricerca apparirebbero altrettanto appetibili. Si puo allora utilizzare una

funzione obiettivo secondaria f (come indicato al passo 3) legata ovviamente

a quella primaria, ma che consenta di ovviare a questo problema.

Tra gli algoritmi che utilizzano la tecnica del tabu search per la risoluzione

di problemi di taglio bidimensionale va citato quello sviluppato da Alvarez-

Valdes et al [2].

43

Capitolo 4

Sviluppo di un algoritmo

GRASP

Per migliorare i risultati prodotti dall’algoritmo euristico costruttivo, ho de-

ciso di sviluppare un algoritmo di tipo GRASP, per il problema di taglio bi-

dimensionale, non a ghigliottina, seguendo il lavoro svolto da Alvarez-Valdes

et al [1].

4.1 Fase Costruttiva

La fase costruttiva coincide con l’algoritmo costruttivo presentato in prece-

denza, introducendo pero delle procedure di scelta casuale dei pezzi e dei

rettangoli.

Definiamo si lo score di un pezzo i (che puo essere uguale a ei o a vi a

seconda del criterio di ordinamento di P prescelto) e smax = max{si|i ∈ P}.A questo punto si puo scegliere tra tre alternative:

1. Selezionare il pezzo i a caso nell’insieme S = {j|sj ≥ δsmax} dove δ e un

parametro da determinare (0 < δ < 1). S e chiamato anche Restricted

Set of Candidates.

2. Selezionare il pezzo i a caso nell’insieme formato dal 100(1 − δ)% dei

pezzi migliori.

45

3. Selezionare il pezzo i tra tutti quelli di P ma con una probabilita di

essere scelto proporzionale allo score si e pari a pi = si∑

sj.

4.1.1 Scelta del parametro δ

Qualora si opti per l’utilizzo della prima o della seconda alternativa, la scelta

del parametro δ risulta fondamentale per ottenere una buona efficienza del-

l’algoritmo. Un’analisi preliminare dimostra che nessun valore di δ produce

sempre il miglior risultato, percio vengono prese in considerazione diverse

strategie, le quali consistono nel far variare il parametro δ in maniera casuale

o sistematica durante il processo iterativo. Queste strategie di scelta sono:

1. Ad ogni iterazione, scegliere δ a caso nell’intervallo [0.4, 0.9].

2. Ad ogni iterazione, scegliere δ a caso nell’intervallo [0.25, 0.75].

3. Ad ogni iterazione, δ assume a turno uno dei seguenti valori: 0.4, 0.5,

0.6, 0.7, 0.8, 0.9.

4. δ = 0.75.

5. Reactive GRASP

Nel Reactive GRASP, proposto da Prais e Ribeiro [18], δ e inizialmente

scelto a caso da un insieme di valori discreti, ma dopo un certo numero

di iterazioni, la qualita delle soluzioni trovate con ogni valore di δ viene

presa in considerazione e la probabilita associata ai valori di δ che

producono le soluzioni migliori viene incrementata. La procedura e

descritta in dettaglio nella Figura 7, seguendo le indicazioni espresse

da Delorme et al [19]. Il valore di α e fissato a 10, come suggerito da

Prais e Ribeiro [18].

46

Inizializzazione:

D = {0.1, 0.2, ..., 0.9}, insieme dei possibili valori di δ

Sbest = 0; Sworst = ∞nδ∗ = 0, numero delle iterazioni con δ∗,∀δ∗ ∈ D.

Sumδ∗ = 0, somma dei valori delle soluzioni ottenute con δ∗.

P (δ = δ∗) = pδ∗ = 1/|D|,∀δ∗ ∈ D, con pδ∗ probabilita associata a δ∗.

numIter = 0

While (numIter < maxIter)

{Scegli δ∗ da D con probabilita pδ∗

nδ∗ = nδ∗ + 1

numIter = numIter + 1

Applica la Fase Costruttiva ottenendo la soluzione S

Applica la Fase di Miglioramento ottenendo la soluzione S ′

If S ′ > Sbest then Sbest = S ′

If S ′ < Sworst then Sworst = S ′

Sumδ∗ = Sumδ∗ + S ′

If mod(numIter, 100) == 0 (ogni 100 iterazioni):

evalδ =

(Sumδ/nδ−Sworst

Sbest−Sworst

∀δ ∈ D

pδ = evalδ(∑δ′∈D evalδ′

) ∀δ ∈ D

}

Figura 7 Reactive GRASP

4.2 Fase di Miglioramento

La fase di miglioramento parte dalla soluzione ottenuta nella fase costruttiva

e consiste in un tentativo di migliorare la soluzione trovata facendo ricorso

ad una procedura di ricerca locale. Sono possibili tre diverse alternative:

47

1. Si seleziona un blocco adiacente ad un rettangolo vuoto e si prende

in considerazione il fatto di ridurne il numero di pezzi o di eliminarlo

completamente. I pezzi rimanenti sono poi spostati verso gli angoli e i

rettangoli vuoti vengono fusi. A questo punto si applica nuovamente la

fase costruttiva alla nuova lista di rettangoli vuoti, inserendo pero prima

i pezzi a maggiore efficienza ei ed utilizzando per primi i rettangoli vuoti

con area minore (Figura 8). Se la soluzione trovata migliora quella della

fase precedente, il processo va avanti e si prende in considerazione un

nuovo blocco.

Selezione Riduzione Spostamento Riempimento

Figura 8 Metodo I

2. Questa seconda procedura e una semplificazione di quella precedente

in quanto i pezzi non vengono mossi verso gli angoli e i nuovi rettangoli

vuoti sono fusi solamente con rettangoli vuoti adiacenti e gia esistenti

(Figura 9).

Selezione Riduzione Riempimento

Figura 9 Metodo II

3. Il terzo metodo consiste nell’eliminare l’ultimo k% dei blocchi inseriti

(per esempio l’ultimo 10%), spostare i pezzi rimanenti verso gli angoli,

48

fondere i rettangoli vuoti ed applicare ai nuovi spazi creati un algoritmo

costruttivo deterministico come quello proposto da Beltran et al [20]

(vedi Figura 10, in cui i numeri nei pezzi riflettono l’ordine in cui questi

sono stati inseriti durante la fase costruttiva).

Selezione Riduzione Riempimento

Figura 10 Metodo III

4.3 Variazione dei vincoli dei pezzi

Durante il processo iterativo si ottiene la miglior soluzione attuale conosciuta

di valore vbest. E’ possibile utilizzare questo valore per far variare il lower

bound Pi di alcuni pezzi, che devono comparire se si vuole migliorare la solu-

zione, e l’upper bound Qi di alcuni pezzi, la cui inclusione non permetterebbe

di migliorare la soluzione.

4.3.1 Aumento dei lower bound Pi

Definiamo totalpieces =∑m

i vi ∗Qi, il valore totale di tutti i pezzi disponibili;

se c’e un pezzo i con Pi < Qi e totalpieces−(Qi−Pi)∗vi ≤ vbest, una soluzione

con il numero minimo di pezzi Pi non puo migliorare la soluzione vbest. Ogni

soluzione migliore deve includere piu pezzi di i e il relativo Pi deve essere

aumentato. Se calcoliamo t come

max t : totalpieces − t ∗ vi > vbest (4.1)

t ≥ 0, t ≤ Qi − Pi (4.2)

49

il nuovo valore di Pi sara Pi = Qi − t. Questo lower bound migliorato puo

essere utile nella fase costruttiva in cui i pezzi con Pi > 0 sono tagliati per

primi, e nella fase di miglioramento in cui i pezzi che raggiungono il loro

lower bound non sono candidati alla rimozione.

4.3.2 Diminuzione degli upper bound Qi

Definiamo R =∑

Pi>0 Pi ∗ li ∗ wi, l’area totale dei pezzi che devono apparire

per forza in ogni soluzione ammissibile, Rv =∑

Pi>0 Pi ∗ vi, il valore di questi

pezzi e emax = max{ei, i = 1, ...,m}, la massima efficienza dei pezzi. Se c’e

un pezzo i con Qi > Pi e ei < emax che soddisfa la disequazione

Qi ∗ li ∗ wi ∗ (emax − ei) ≥ emax ∗ (L ∗W −R) + Rv − vbest (4.3)

ogni soluzione con Qi copie del pezzo i non puo migliorare la soluzione vbest.

Quindi ogni soluzione migliore dovra avere al massimo un numero di pezzi

di i minore di Qi. Se calcoliamo t come

max t : t ∗ li ∗ wi ∗ (emax − ei) < emax ∗ (L ∗W −R) + Rv − vbest (4.4)

t ≥ 0, t ≤ Qi − Pi (4.5)

il nuovo valore di Qi sara Qi = Pi + t. Questa diminuzione dell’upper bound

puo essere utile in entrambe le fasi, soprattutto se Qi dovesse essere posto

a 0, il pezzo i dovrebbe essere rimosso, favorendo un notevole risparmio

computazionale nella scelta dei pezzi.

4.4 Scelta delle migliori strategie

Prima di implementare l’algoritmo GRASP, e bene sciogliere alcuni nodi ri-

masti in sospeso. Si tratta di scegliere le opzioni migliori tra quelle fornite

nelle varie strategie sopra discusse. Per effettuare questa scelta, ho fatto teso-

ro dell’esperienza computazionale svolta da Alvarez-Valdes et al [1]. Quindi

l’algoritmo GRASP completo dovra utilizzare le seguenti strategie:

50

• Selezione del pezzo: il pezzo che produce il maggior incremento nella

funzione obiettivo.

• Selezione del rettangolo: casuale.

• Procedura di randomizzazione: Restricted Candidate List.

• Selezione del parametro δ: Reactive GRASP.

• Fase di miglioramento: metodo 3 con rimozione del 10% dei pezzi.

• Numero di iterazioni: 10000.

L’algoritmo deve arrestarsi quando ha raggiunto il limite massimo di ite-

razioni oppure quando la soluzione trovata risulti uguale al corrispondente

upper bound.

Stando sempre all’esperienza di Alvarez-Valdes et al, l’incremento dei lo-

wer bound Pi dei pezzi non produce significativi vantaggi sulle performance

dell’algoritmo (e pertanto non verra implementato), mentre l’incremento de-

gli upper bound Qi ha un effetto considerevole, specialmente nei problemi in

cui ci sono differenze notevoli tra le efficienze dei pezzi. In media, piu del

60% dei pezzi sono scartati non appena vengono trovate buone soluzioni.

51

Capitolo 5

Sviluppo di un algoritmo Tabu

Search

Per comparare i risultati ottenuti con l’algoritmo GRASP e per cercare di

migliorarli ulteriormente, ho deciso di sviluppare anche un algoritmo che

utilizzi la tecnica metaeuristica del Tabu Search. Per far cio ho seguito con

attenzione il lavoro svolto da Alvarez-Valdes et al [2]. Gli elementi base

dell’algoritmo sono descritti nelle sezioni seguenti.

5.1 Definizione delle mosse

Lo spazio delle soluzioni, in cui e possibile muoversi, e composto solo da

soluzioni ammissibili. In questo spazio vengono definite diverse mosse per

muoversi da una soluzione ad un’altra. Una soluzione iniziale puo essere

ottenuta attraverso l’applicazione del gia citato algoritmo costruttivo.

Distinguiamo tra due tipi di mosse: una riduzione di un blocco e un in-

serimento di un blocco. Nella riduzione viene ridotta la dimensione di un

blocco esistente, eliminando alcune delle sue righe o colonne. Nell’inserimen-

to, un nuovo blocco e aggiunto alla soluzione. Per entrambe le mosse verra

presentato prima uno schema della procedura e poi un esempio dettagliato.

53

5.1.1 Riduzione di un blocco

La riduzione delle dimensioni di un blocco avviene seguendo i seguenti passi

elementari:

1. Inizializzazione

L=lista dei rettangoli vuoti della soluzione corrente

C=lista dei blocchi della soluzione corrente

2. Scelta del blocco da ridurre

Per prima cosa bisogna scegliere b, uno dei blocchi di C, formato da k

colonne e l righe del pezzo i. Poi va selezionato il numero di colonne

(righe) da eliminare,

1 ≤ r ≤ k (1 ≤ r ≤ l),

mantenendo il numero dei pezzi nella soluzione xi ≥ Pi. Se Pi = 0, il

blocco puo scomparire completamente. Il nuovo rettangolo vuoto R va

aggiunto a L.

3. Spostamento dei blocchi rimanenti verso gli angoli piu vicini

La lista dei rettangoli vuoti L e aggiornata secondo questo criterio.

4. Riempimento dei rettangoli vuoti con nuovi blocchi

A questo punto va applicato l’algoritmo costruttivo partendo dalle com-

posizioni attuali di L e C, e con P contenente i pezzi che possono ancora

essere tagliati e aggiunti alla soluzione corrente. Prima di procedere con

il processo costruttivo, i rettangoli vuoti di L devono essere fusi, per

adattarsi in modo migliore alla lista dei pezzi P .

Quando viene selezionato il nuovo pezzo da tagliare, non deve essere

preso in considerazione il pezzo i del blocco ridotto, finche un altro

pezzo non sia stato incluso nella soluzione modificata.

5. Fusione dei blocchi con la stessa struttura

Infine e necessario provare a fondere blocchi dello stesso pezzo i con

la stessa lunghezza o la stessa larghezza, se sono adiacenti e hanno un

54

lato in comune, oppure se uno di essi puo essere spostato in modo da

renderli adiacenti.

a) selezione b) riduzione

c) movimento agli angoli d) riempimento

Figura 11 Riduzione di un blocco

a) riduzione b) spostamento c) fusione d) riempimento

Figura 12 Rettangoli vuoti nella mossa di riduzione

Nella Figura 11 vediamo un esempio di mossa di riduzione su un’istanza

proposta da Jakobs [23] e usata in seguito da Leung et al [14]. Il rettangolo

base e R = (120, 45), m = 22 e M = 25 pezzi che possono essere tagliati

da esso, riempiendolo completamente. La Figura 11(a) mostra una soluzione

con 23 pezzi nella quale non possono essere inseriti i due pezzi (6, 12). L e

composta da R1 = (60, 24, 72, 30) e R2 = (72, 18, 84, 24) (in grigio chiaro).

55

Per prima cosa un blocco composto da un pezzo (12, 21) viene selezionato e

ridotto, cosı facendo scompare dalla soluzione creando un nuovo rettangolo

vuoto R3 = (72, 24, 84, 45) che viene aggiunto a L (Figura 11(b)). A questo

punto un blocco composto da un pezzo (12, 15) viene mosso verso l’angolo

in alto a destra. Quindi L = {R1, R2, R4, R5} dove R4 = (60, 30, 72, 45) e

R5 = (72, 24, 84, 30) (Figura 11(c)). In seguito l’algoritmo costruttivo riempie

i rettangoli vuoti. Per prima cosa vengono fusi R1 e R4 per formare R6 =

(60, 24, 72, 45), e R2 e R5 per formare R7 = (72, 18, 84, 30). Poi R7 viene

selezionato e riempito completamente con due pezzi (6, 12). Infine R6 viene

riempito con il pezzo eliminato all’inizio. La soluzione finale, che e anche

quella ottima, e mostrata nella Figura 11(d).

5.1.2 Inserimento di un blocco

L’inserimento di un nuovo blocco avviene eseguendo la sequenza di passi

indicata qui sotto:

1. Inizializzazione

L=lista dei rettangoli vuoti della soluzione corrente

C=lista dei blocchi della soluzione corrente

2. Scelta del blocco da inserire

Bisogna prendere un pezzo i con xi ≤ Qi, in modo tale da formare un

blocco con k colonne e l righe (k ∗ l ≤ Qi − xi) di pezzi di tipo i.

3. Selezione della posizione in cui inserire il nuovo blocco

Due strategie sono state prese in considerazione per scegliere la posizio-

ne del nuovo blocco. In entrambi i casi il blocco e posizionato in modo

da ricoprire parzialmente o totalmente uno o piu rettangoli vuoti.

• Per ogni rettangolo vuoto, si considerano le quattro alternative in

cui un angolo del rettangolo e scelto per il corrispondente angolo

del blocco. Se le dimensioni del blocco sono maggiori di quelle

del rettangolo, il blocco puo ricoprire altri blocchi oppure puo

occupare parte di altri rettangoli vuoti.

56

• Si seleziona solo un rettangolo vuoto in modo che, posizionando il

blocco con il suo angolo inferiore sinistro sopra l’angolo inferiore

sinistro del rettangolo, venga massimizzata l’area ricoperta. Per

questo rettangolo vengono considerate le quattro alternative viste

sopra.

4. Rimozione dei pezzi della soluzione che si sovrappongono al blocco in-

serito

Aggiornamento di C (alcuni blocchi vengono ridotti o eliminati)

Aggiornamento di L (nuovi rettangoli vuoti possono comparire)

5. Riempimento dei rettangoli vuoti con nuovi blocchi

6. Fusione dei blocchi con la stessa struttura

Questi ultimi due punti coincidono con gli ultimi due della riduzione

dei blocchi.

Nella Figura 13 vediamo un esempio della seconda strategia di inseri-

mento dei blocchi nei rettangoli. Nella Figura 13(a) due rettangoli vuoti,

in grigio, sono presi in considerazione per l’inserimento del nuovo blocco, in

bianco. Poiche l’area occupata maggiore corrisponde al rettangolo 1, viene

scelto quest’ultimo. Nella Figura 13(b), vengono mostrate le quattro possi-

bili alternative per inserire il nuovo blocco nei corrispondenti quattro angoli

del rettangolo.

a) Scelta del rettangolo b) Possibili posizioni

Figura 13 Selezione della posizione del nuovo blocco

57

a) Soluzione iniziale b) Inserimento

c) Eliminazione sovrapposizioni d) Fusione e riempimento

Figura 14 Inserimento di un blocco

Nella Figura 14 vediamo un esempio dell’utilizzo della mossa di inseri-

mento su un’istanza proposta da Fekete e Schepers [9] e in seguito utilizzata

da Beasley [6]. Il rettangolo base e R = (100, 100), m = 15 e M = 50

pezzi che possono essere tagliati da esso. La Figura 14(a) mostra una so-

luzione di valore z = 27539. La lista dei rettangoli vuoti L e formata da

R1 = (70, 41, 72, 81) e R2 = (72, 80, 100, 81). Per prima cosa viene sele-

58

zionato il pezzo i = 5 di dimensioni (6, 40) con Qi = 5 e solo due copie

nella soluzione attuale, e viene scelto un blocco b formato da un solo pezzo.

In seguito viene posizionato tale blocco sopra R1, sovrapponendo l’angolo

in alto a sinistra del blocco con l’angolo in alto a sinistra del rettangolo.

Il blocco b ricopre completamente R1 e parzialmente R2, il quale diventa

R3 = (76, 80, 100, 81). Il blocco ricopre in parte anche un blocco esistente

(Figura 14(b)). Quindi vengono rimossi i pezzi della soluzione iniziale che

si sovrappongono al nuovo blocco inserito, producendo due nuovi rettangoli

R4 = (76, 40, 78, 80) e R5 = (72, 40, 76, 41) (Figura 14(c)). A questo punto la

procedura di riempimento inizia con L = {R£, R4, R5}. Prima pero R3 e R4

vengono fusi generando R6 = (76, 40, 78, 81) e R7 = (78, 80, 100, 81). Mentre

nessuno dei pezzi poteva entrare in R3 o R4, ora invece un pezzo i = 13 di

dimensione (2, 41) puo essere inserito in R6. La nuova soluzione e migliore

di quella iniziale ed ha un valore z∗ = 27718, ottima per il problema (Figura

14(d)).

5.2 Mosse da esaminare

Ad ogni iterazione vengono esaminate tutte le possibili mosse di riduzione e

inserimento che possono essere applicate alla soluzione corrente.

• Riduzione:

1. Bisogna prendere ogni blocco della soluzione, uno per volta, in

ordine casuale.

2. Vanno considerate tutte le possibilita di riduzione nelle direzioni

adiacenti ai rettangoli vuoti.

• Inserimento:

1. Bisogna selezionare un pezzo per cui il numero di copie xi nella

soluzione e minore di Qi, uno per volta, in ordine casuale.

2. Vanno considerati tutti i blocchi che e possibile costruire con

questo pezzo.

59

3. Vanno considerate tutte le alternative per posizionare il blocco

all’interno di un rettangolo vuoto.

5.3 Selezione della mossa

La funzione obiettivo consiste solo nel massimizzare il valore dei pezzi tagliati

f(x) =∑

i vixi. Tuttavia, se le mosse fossero valutate in accordo con questa

funzione, potrebbero esserci della mosse con la stessa valutazione. Quindi,

in caso di valori uguali della funzione obiettivo, e possibile valutare la mossa

attraverso l’utilizzo di una funzione obiettivo secondaria g(x) = k1S+k2|L|+k3C + k4F .

• S (Simmetria): e preferibile non esplorare soluzioni simmetriche, ma

vengono preferite soluzioni in cui i rettangoli vuoti sono piu concentrati

nella parte destra e in quella in alto del rettangolo base. Se non esiste

una soluzione simmetrica e se i rettangoli vuoti sono concentrati a

destra e in alto, allora S = 1, altrimenti S = 0.

• |L| (Numero dei rettangoli vuoti): se e possibile, vengono preferite

soluzioni in cui il numero di rettangoli vuoti e il piu basso possibile.

• C (Rettangoli vuoti accentrati e concentrati): sono preferibili soluzioni

in cui i rettangoli vuoti sono accentrati e concentrati il piu possibile,

perche in questo modo e piu semplice eseguire fusioni ed ottenere spazi

per nuovi pezzi. Dato ER il piu piccolo rettangolo che contiene tutti i

rettangoli vuoti,

C = 1− (0.75 ∗ rd + 0.25 ∗ ra)

dove rd e la distanza tra il centro di ER e il centro del rettangolo base,

diviso la distanza tra il centro del rettangolo base e il suo angolo in

basso a sinistra, e ra e l’area di ER diviso l’area del rettangolo base.

• F (Ammissibilita). Nei problemi di tipo doubly constrained, la soluzione

iniziale puo non essere ammissibile. In questo caso F = 1, altrimenti

F = 0.

60

Questi criteri sono aggiunti alla funzione obiettivo secondaria con alcuni coef-

ficienti che riflettono la loro relativa importanza, in accordo con i risultati

preliminari ricavati da un sottoinsieme di problemi. In questa implementa-

zione questi fattori assumono i seguenti valori:

Criterio Coefficiente Valore

Simmetria k1 5000

Rettangoli vuoti k2 -950

Rettangoli accentrati k3 50

Ammissibilita k4 -50000

5.4 Tabu list

La tabu list contiene, per ogni soluzione, la seguente coppia di attributi: il

valore della funzione obiettivo e il piu piccolo dei rettangoli ER contenente

tutti i rettangoli vuoti. Una mossa e quindi tabu se questi due attributi della

nuova soluzione coincidono con una delle coppie della tabu list.

La dimensione della tabu list varia in maniera dinamica. Dopo un certo

numero di iterazioni senza migliorare la soluzione, le lunghezza e scelta a

caso nell’intervallo [0.25 ∗M, 0.75 ∗M ], dove M =∑

i Qi.

Il criterio di aspirazione permette di muoversi verso una soluzione tabu

se essa migliora la soluzione ottenuta finora.

5.5 Intensificazione e diversificazione

Le mosse che sono state definite sopra consentono un alto livello di diversifi-

cazione. Tuttavia sono possibili due ulteriori strategie di diversificazione:

• Memoria a lungo termine

Durante il processo di ricerca, viene mantenuta in memoria la frequenza

di ogni pezzo che appare nelle soluzioni. Questa informazione e utiliz-

zata sia per la diversificazione che per l’intensificazione.

61

Quando viene usata la diversificazione, vengono favorite le mosse che

coinvolgono pezzi che non appaiono molto frequentemente nelle solu-

zioni, in modo da favorire l’apparizione di nuovi pezzi.

Quando viene usata per l’intensificazione, vengono considerati solo i

pezzi che corrispondono a soluzioni di alta qualita, in modo da favorire

questi pezzi e far sı che appaiano ancora in nuove soluzioni.

Durante la fase di diversificazione, la funzione obiettivo e modificata

sottraendo un termine che e la somma delle frequenze dei pezzi che

appaiono nelle soluzioni:

f(x) → f(x)−∑

i

freq(pi)

Durante una fase di intensificazione, la funzione obiettivo e modificata

aggiungendo un termine che riflette la frequenza dei pezzi nell’insieme

delle soluzioni d’elite E :

f(x) → f(x) + K∑i∈E

freq(pi)

• Ripartenza

Stando a quanto detto prima a riguardo della funzione obiettivo se-

condaria, vengono preferite le soluzioni che soddisfano il criterio di

simmetria. Dopo un certo numero di iterazioni senza migliorare la mi-

glior soluzione trovata, la soluzione attuale viene sostituita da quella

che si ottiene eseguendo una simmetria orizzontale e una verticale su di

essa. La nuova soluzione ottenuta in questa maniera dovrebbe essere

abbastanza diversa da quelle esaminate di recente ed e assunta come il

nuovo punto di partenza per la ricerca.

5.6 Variazione dei vincoli dei pezzi

La variazione dei lower bound Pi e degli upper bound Qi dei pezzi, per au-

mentare l’efficienza dell’algoritmo, avviene in maniera del tutto identica a

quanto specificato nell’omonima sezione dell’algoritmo GRASP.

62

5.7 Path relinking

Il Path Relinking e un approccio per integrare diversificazione e intensifica-

zione nel contesto del Tabu Search (Glover e Laguna [24]). Questo approccio

genera nuove soluzioni esplorando traiettorie che collegano soluzioni di alta

qualita. Partendo da una di queste soluzioni, chiamata soluzione iniziale,

viene generato un percorso nello spazio delle soluzioni che conduca ad un’al-

tra soluzione, chiamata soluzione guida. Cio e possibile selezionando mosse

che introducano gli elementi della soluzione guida nelle nuove soluzioni.

Durante tutto il processo di ricerca viene mantenuto un insieme di solu-

zioni d’elite, cioe le migliori soluzioni trovate. Per prima cosa vengono prese

queste soluzioni d’elite a coppie: una della due, la soluzione A, viene consi-

derata la soluzione iniziale, mentre l’altra, la soluzione B, viene considerata

la soluzione guida. Poiche l’algoritmo tabu search favorisce le soluzioni con

rettangoli vuoti preferibilmente nella parte in alto a destra del rettangolo

base, entrambe le soluzioni tenderanno ad avere i rettangoli vuoti in questa

zona. Quindi viene applicata una simmetria verticale alla soluzione iniziale

prima di iniziare il processo di Path Relinking.

Seguendo una strategia costruttiva, vengono inseriti i blocchi della solu-

zione B, uno per volta, nella soluzione A. La mossa di inserimento segue la

procedura descritta in precedenza. I pezzi che si sovrappongono al blocco

vengono eliminati, i rimanenti blocchi vengono mossi verso gli angoli del ret-

tangolo base piu vicini, e i rettangoli vuoti che si formano vengono riempiti.

Alla fine del processo, si sara riprodotta la soluzione B, ma durante il percor-

so saranno state generate nuove soluzioni. Quando vengono inseriti i blocchi

della soluzione B, devono essere inseriti per primi i blocchi adiacenti ai lati

del rettangolo base e solo in seguito i blocchi nel centro.

Un esempio del primo passo del Path Relinking appare nella Figura 15.

La soluzione A con il valore 5376, viene selezionata come soluzione iniziale

(Figura 15(a)) mentre la soluzione B con il valore 5352, come soluzione guida

(Figura 15(b)). La simmetria verticale applicata alla soluzione A produce la

soluzione di Figura 15(c). A questo punto un blocco di B viene inserito in

63

essa (Figura 15(d)). I pezzi che si sovrappongono ad esso vengono cancellati

cosı come un pezzo dello stesso tipo del pezzo appena inserito, in modo da

non eccedere Qi. I blocchi rimanenti vengono spostati verso gli angoli piu

vicini, come indicato dalle frecce nella Figura 15(e). Infine gli spazi vuoti

vengono fusi e riempiti con nuovi pezzi, producendo la soluzione di Figura

15(f). Questa soluzione e diversa dalle soluzioni A e B ed ha valore 5395.

a) Soluzione A: 5376 b) Soluzione B: 5352 c) Simmetria su A

d) Inserimento blocco di B e) Eliminazione sovrapposizioni f) Fusione e riempimento

Figura 15 Path Relinking - Istanza 2 da Jaboks [23]

64

5.8 Scelta delle migliori strategie

Anche in questo caso, prima di implementare l’algoritmo Tabu Search e utile

impostare le varie strategie in modo da ottenere i risultati migliori. Tenendo

in considerazione il lavoro svolto da Alvarez-Valdes et al [2] ho deciso di

utilizzare le seguenti strategie:

• Tabu list dinamica: variazione delle dimensioni dopo 100 iterazioni in

cui non si migliora la soluzione.

• Diversificazione: si effettua dopo 100 iterazioni senza miglioramenti, e

si esegue per le successive 100 iterazioni (o finche non si migliora la

soluzione).

• Intensificazione: si effettua dopo 100 iterazioni senza miglioramenti, e

si esegue per le successive 100 iterazioni (o finche non si migliora la

soluzione), con K = 100 e considerando un’elite composta dalle 10

migliori soluzioni.

• Ripartenza: se al termine delle fasi di diversificazione e intensificazione

non si e migliorata la soluzione, si eseguono una simmetria verticale e,

contemporaneamente, una orizzontale.

• Scelta del rettangolo nella mossa di inserimento: se il problema e

unweighted si utilizza il metodo 1, altrimenti si ricorre al metodo 2.

• Numero di iterazioni: 1500.

Anche in questo caso l’algoritmo deve fermarsi non appena viene raggiunto

il limite massimo di iterazioni, o quando la soluzione sia uguale al corrispon-

dente upper bound.

Per quanto riguarda la variazione dei vincoli dei pezzi, anche per l’algorit-

mo Tabu Search si e scelto di implementare solo la procedura che permette la

diminuzione degli upper bound Qi, poiche e l’unica che garantisce significativi

miglioramenti.

65

Cosı come si e deciso di non implementare la procedura di Path Relin-

king poiche anche quest’ultima non riuscirebbe a migliorare le prestazioni

dell’algoritmo.

66

Capitolo 6

Implementazione

Per l’implementazione degli algoritmi euristici descritti in precedenza, mi

sono avvalso del linguaggio di programmazione C che, grazie alle sue poten-

zialita e alla sua modularita, ben si adatta alla realizzazione degli algoritmi,

rendendo possibile un ampio riutilizzo del codice. Dato che il C e un lin-

guaggio ”compilato”, e possibile ottenere ottimi risultati in termini di tempi

computazionali.

6.1 Strutture dati

Le strutture dati complesse, presenti nell’algoritmo, sono principalmente tre:

i pezzi da tagliare, i rettangoli vuoti e i blocchi inseriti. A queste tre poi se

ne aggiungono altre secondarie utilizzate solo nell’algoritmo Tabu Search e

definite in dettaglio in seguito. Per tutte quante si e deciso di utilizzare una

delle strutture dati del linguaggio C, ovvero la lista lineare. La lista lineare e

una successione di elementi omogenei che occupano in memoria una posizione

qualsiasi. Ciascun elemento contiene una o piu informazioni e un puntatore

all’elemento successivo. L’accesso alla lista avviene con il puntatore al primo

elemento.

Elemento = informazioni + puntatore

67

6.1.1 Lista dei pezzi P

La lista dei pezzi P comprende, oltre all’indice i che identifica il pezzo, anche

la lunghezza l, la larghezza w, il lower bound P , l’upper bound Q, il valore v

e l’efficienza e. Pertanto, la struttura che definisce tale lista e:

1 struct pezzo{2 int i;

3 int l;

4 int w;

5 int P;

6 int Q;

7 int v;

8 double e;

9 struct pezzo *pun;

10 };

E’ da notare come tutti i dati siano int, mentre l’efficienza puo assumere un

valore non intero ed e pertanto stata rappresentata tramite un double.

6.1.2 Lista dei rettangoli L

Un rettangolo vuoto e identificato dalle coordinate (x, y) rispettivamente

dell’angolo in basso a sinistra e dell’angolo in alto a destra. Pertanto la lista

dei rettangoli L e definita come:

1 struct rettangolo{2 int x1;

3 int y1;

4 int x2;

5 int y2;

6 struct rettangolo *pun;

7 };

68

6.1.3 Lista dei blocchi C

Un blocco, invece, e caratterizzato oltre che dalle coordinate degli angoli,

come i rettangoli, anche dall’indice i e dal valore v del pezzo che lo compone,

nonche dal numero k dei pezzi per colonna e dal numero l dei pezzi per riga.

Quindi, la lista dei blocchi C e cosı costituita:

1 struct blocco{2 int i;

3 int x1;

4 int y1;

5 int x2;

6 int y2;

7 int v;

8 int k;

9 int l;

10 struct blocco *pun;

11 };

6.1.4 Lista delle soluzioni d’elite E

Gli elementi che compongono la lista delle soluzioni d’elite sono formati dal

valore di una soluzione v e dalla lista dei blocchi b, generata da tale soluzione:

1 struct elite{2 int v;

3 struct blocco *b;

4 struct elite *pun;

5 };

69

6.1.5 Lista dei tabu T

La tabu list e composta da coppie formate dal valore di una soluzione v e

dal piu piccolo rettangolo ER in grado di contenere tutti i rettangoli vuoti

generati da tale soluzione. Il rettangolo ER e identificato ovviamente dalla

quadrupla (x1, y1, x2, y2):

1 struct tabu{2 int v;

3 int x1;

4 int y1;

5 int x2;

6 int y2;

7 struct tabu *pun;

8 };

6.1.6 Lista delle frequenze dei pezzi F

La lista delle frequenze tiene conto di quante volte un pezzo entra a far parte

di una soluzione. Quindi gli elementi di tale lista saranno formati dall’indice

i di un pezzo e dal numero di occorrenze n:

1 struct frequenza{2 int i;

3 int n;

4 struct frequenza *pun;

5 };

6.2 Caricamento dei dati iniziali

Il caricamento dei dati avviene attraverso l’utilizzo di un file di testo. Tale

file deve essere realizzato seguendo obbligatoriamente lo schema seguente:

70

N

n1

L1 W1

l1 w1 P1 Q1 v1. . . ln wn Pn Qn vn...

nN

LN WN

l1 w1 P1 Q1 v1. . . ln wn Pn Qn vn

dove N e il numero di problemi presenti nel file, ni, Li e Wi sono rispet-

tivamente il numero di pezzi dell’istanza, la lunghezza e la larghezza del

rettangolo base, e lj, wj, Pj, Qj e vj sono la lunghezza, la larghezza, il lower

bound, l’upper bound e il valore di un singolo pezzo.

6.3 Schema dell’algoritmo GRASP

Per realizzare l’algoritmo GRASP ho utilizzato una metodologia di tipo top-

down: partendo dallo schema generale dell’algoritmo, ho implementato man

mano le varie funzioni, sintetizzando prima quelle principali e proseguendo

poi con quelle secondarie. Tale schema e rappresentato qui di seguito:

1 void main()

2 {3 apriFile();

4 for (int index = 1; index <= problemi; index++){5 caricaDatiProblema();

6 inizializzaParametri();

7 ub = upperBound();

8 while (numIter < maxIter){9 numIter++;

10 S0 = randomConstructive();

71

11 S1 = improvement();

12 if (!isFeasible(P))

13 continue;

14 if (S1 > Sbest){15 Sbest = S1;

16 salvaSoluzioneMigliore(P, L, C);

17 if (Sbest == ub)

18 break;

19 decreasingQi(P);

20 }21 if (S1 < Sworst)

22 Sworst = S1;

23 nDelta[iDelta]++;

24 sumDelta[iDelta] = sumDelta[iDelta] + S1;

25 if ((numIter % 100) == 0){26 denom = Sbest - Sworst;

27 totalEval = 0;

28 for (int j = 0; j < 9; j++){29 mean = sumDelta[j] / nDelta[j];

30 numer = mean - Sworst;

31 evalDelta[j] = pow(numer / denom, alfa);

32 totalEval = totalEval + evalDelta[j];

33 }34 for (int j = 0; j < 9; j++)

35 probDelta[j] = evalDelta[j] / totalEval;

36 }37 }38 visualizzaRisultati();

39 eliminaListe();

40 }41 chiudiFile();

42 }

72

Dallo schema si evince che l’algoritmo GRASP consiste nell’esecuzione rei-

terata delle fasi costruttiva (nella sua versione randomConstructive) e di

miglioramento (improvement). Al termine di queste fasi, se la soluzione e

ammissibile (isFeasible), si confronta il risultato ottenuto con il migliore a

disposizione; se questo e maggiore, la nuova soluzione trovata viene salvata

(salvaSoluzioneMigliore) e, se quest’ultima ha un valore uguale all’upper

bound, l’algoritmo termina all’istante.

In questa parte dell’algoritmo vengono aggiornati anche parametri come

la somma delle soluzioni ottenute con un dato fattore δ (sumDelta) e il

numero di iterazioni in cui si e fatto ricorso a tale valore di δ (nDelta). Infine,

ogni 100 iterazioni, avviene l’aggiornamento delle probabilita (probDelta)

associate all’utilizzo dei valori ammissibili di δ.

6.4 Funzioni dell’algoritmo GRASP

int randomConstructive()

La funzione randomConstructive e la funzione cardine di tutto l’algoritmo

GRASP e coincide con la prima fase, quella costruttiva. Per prima cosa viene

scelto il valore del parametro δ tenendo conto delle probabilita associate ai

suoi possibili valori, poi si definisce la soglia tra i valori dei pezzi e viene

costruita la Restricted Candidate List (creaRCL).

A questo punto, finche esiste un pezzo in grado di entrare in uno qualun-

que dei rettangoli vuoti si procede col selezionare in maniera casuale (rand)

sia il pezzo da tagliare che il rettangolo vuoto. Se il pezzo non puo essere

inserito nel rettangolo (fill), vengono selezionati un pezzo e un rettangolo

nuovi, sempre in maniera casuale. Se il pezzo, invece, e in grado di entrare

nel rettangolo, si procede scegliendo, sempre in maniera casuale, sia il nume-

ro di colonne, che il numero di righe del pezzo che formeranno il blocco da

tagliare, sempre pero rispettando il limite Qi.

Quindi, si prosegue scegliendo l’angolo del rettangolo vuoto in cui inserire

73

il blocco, per la precisione si sceglie quello con distanza (distanza) minima

dal corrispettivo angolo del rettangolo base (distanzaMin). Fatto questo

si procede con la rimozione del rettangolo utilizzato (rimuoviRettangolo),

con l’inserimento del blocco nella lista dei blocchi (aggiungiBlocco), con

l’inserimento di uno o piu rettangoli vuoti (aggiungiRettangolo) e con il

modificare i parametri Pi e Qi del pezzo (se Qi = 0, il pezzo viene rimos-

so con rimuoviPezzo). Infine si procede con l’eseguire tutte le fusioni che

migliorano l’inserimento dei pezzi (merge). Quando non ci sono piu pezzi

in grado di essere tagliati, la procedura termina restituendo il valore della

soluzione trovata.

int improvement()

La funzione improvement realizza la fase di miglioramento discussa in pre-

cedenza. Per prima cosa vengono rimossi dalla lista dei blocchi il k% di essi

(rimuoviBlocco), poi viene aggiornata la lista dei pezzi, aggiungendo quelli

appena ripristinati (aggiungiPezzo), e poi vengono inseriti i rettangoli vuo-

ti generati dalla rimozione dei blocchi. In seguito vengono ordinati i pezzi

secondo i criteri espressi in precedenza (ordinaPezzi) e vengono valutate

tutte le fusioni tra i rettangoli. Infine viene eseguito l’algoritmo costrutti-

vo classico (constructive) che termina con la restituzione del nuovo valore

della soluzione.

struct pezzo *creaRCL(double soglia, struct pezzo *p)

Restituisce la lista dei pezzi composta solamente da quei pezzi con Pi > 0 o

con vi ≥ soglia (Restricted Candidate List).

int vMax(struct pezzo *p)

Restituisce il piu grande tra tutti i valori v dei pezzi.

74

6.5 Schema dell’algoritmo Tabu Search

Anche nel caso dell’algoritmo Tabu Search ho utilizzato una metodologia di

tipo top-down in cui, partendo da uno schema generale, ho implementato di

volta in volta le varie funzioni, partendo da quelle principali e proseguendo

con quelle secondarie. Poiche le funzioni dell’algoritmo GRASP sono state

sviluppate sapientemente in maniera generale, e stato possibile riutilizzare un

gran numero di queste funzioni, cosı com’erano, anche nell’algoritmo Tabu

Search. Lo schema generale e cosı definito:

1 void main()

2 {3 apriFile();

4 for (int index = 1; index <= problemi; index++){5 caricaDatiProblema();

6 inizializzaParametri();

7 ub = upperBound();

8 S = constructive();

9 Start = S;

10 Sbest = S;

11 salvaSoluzioneIniziale(P, L, C);

12 salvaSoluzioneMigliore(P, L, C);

13 tabuList = aggiungiTabu(S, creaER(L), tabuList);

14 frequenze = inizializzaFrequenza(P);

15 while (numIter < maxIter){16 numIter++;

17 valutaRiduzione();

18 valutaInserimento();

19 salvaSoluzioneIniziale(P, L ,C);

20 frequenze = aggiornaFrequenza(C, frequenze);

21 if (numIter < numElite)

22 aggiungiSoluzioneElite(C, S, E);

75

23 else

24 sostituisciSoluzioneElite(C, S, E);

25 if (nTabu < nTabuMax){26 nTabu++;

27 tabuList = aggiungiTabu(S, creaER(L), tabuList);

28 }29 else{30 rimuoviTabu(tabuList);

31 aggiungiTabu(S, creaER(L), tabuList);

32 }33 if (Sstart > Sbest){34 numIterNotImpl = 0;

35 diversification = 0;

36 intensification = 0;

37 Sbest = Sstart;

38 salvaSoluzioneMigliore(P, L, C);

39 decreasingQi(P);

40 }41 else{42 numIterNotImpl++;

43 if ((numIterNotImpl % 100) == 0)

44 tabuList = modificaDimensioniTabuList(tabuList);

45 if ((numIterNotImpl % 400) == 0)

46 diversification = 1;

47 if ((numIterNotImpl % 400) == 0)

48 intensification = 1;

49 if ((numIterNotImpl % 500) == 0){50 diversification = 0;

51 intensification = 0;

52 restartingL(L);

53 restartingC(C);

54 }

76

55 }56 }57 visualizzaRisultati();

58 eliminaListe();

59 }60 chiudiFile();

61 }

Dallo schema si vede come l’algoritmo Tabu Search inizi con l’applicazione

dell’algoritmo costruttivo (constructive) e con il salvataggio della soluzione

trovata (salvaSoluzioneMigliore e salvaSoluzioneIniziale).

A questo punto inizia un processo iterativo in cui vengono valutate tutte

le mosse che e possibile eseguire tra quelle di riduzione (valutaRiduzione)

e di inserimento (valutaInserimento). La mossa che e ritenuta la miglio-

re viene eseguita e, dalla soluzione cosı trovata, si riparte per la prossima

iterazione. Quindi viene inserita (nel rispetto delle dimensioni nTabuMax

della tabu list) la mossa effettuata, tra quelle tabu (aggiungiTabu). Poi

vengono aggiornate le frequenze dei pezzi (aggiornaFrequenza) e viene va-

lutato se la soluzione trovata e meritevole di entrare tra le soluzioni di elite

(aggiungiSoluzioneElite).

Se la nuova soluzione iniziale e maggiore di quella attuale, quest’ultima

viene sostituita (se e uguale all’upper bound, l’algoritmo termina). Altrimen-

ti, se non c’e un miglioramento della soluzione, viene valutato il numero di

iterazioni da cui non si ottiene un miglioramento (numIterNotImpl) e, se

questo numero e multiplo di 100, viene variata la dimensione della tabu li-

st (modificaDimensioniTabuList). Se non si verificano miglioramenti da

400 iterazioni, vengono ”attivate” le fasi di diversificazione e intensificazione

che provvedono ad alterare il valore della funzione obiettivo. Qualora do-

po ulteriori 100 iterazioni, la soluzione non dovesse essere ancora migliorata,

vengono interrotte le fasi di diversificazione e intensificazione e viene eseguita

un’operazione di ripartenza sulla lista dei blocchi (restartingC) e sulla lista

dei rettangoli vuoti (restartingL).

77

6.6 Funzioni dell’algoritmo Tabu Search

void valutaRiduzione()

La funzione esegue tutte le possibili riduzioni dei blocchi esistenti. Per far

questo, vengono selezionati a turno tutti i blocchi, finche non si giunge al-

la fine della lista, e se questo blocco e adiacente ad un rettangolo vuoto

(isAdjacent) si esegue la riduzione (reduction) valutando tutte le possibili

combinazioni di righe e colonne, da entrambe le direzioni: sinistra e destra

per le colonne, e alto e basso per le righe. Al termine della riduzione, si

valuta la mossa eseguita (valutaMossa) e si prosegue con quella successiva.

void valutaInserimento()

La funzione esegue tutti i possibili inserimenti di blocchi. Per prima cosa ven-

gono selezionati tutti i pezzi della lista, uno alla volta, finche non si raggiunge

il termine. Di questo pezzo vengono valutate, a turno, tutte le sue possibili

combinazioni di righe e colonne. Per ognuna di queste combinazioni si prova

ad inserire il blocco (insertion) in tutti i rettangoli (nel caso di problemi di

tipo unweighted), oppure solamente nel rettangolo con area comune massima

(bestRettangolo), posizionandolo alternativamente, se possibile, in tutti e

quattro gli angoli. Una volta eseguito l’inserimento, viene valutata la mossa

appena effettuata (valutaMossa) e si passa a quella successiva.

void reduction(int i, int k, int l)

La funzione riduce di k colonne e l righe un blocco di indice i. Per far questo

modifica i relativi indici del blocco (o lo rimuove totalmente nel caso fosse

stato selezionato per intero per la riduzione) e aggiunge i pezzi rimossi e i

rettangoli vuoti che si vengono a formare, alle relative liste. A questo pun-

to vengono eseguiti un ordinamento dei pezzi e una fusione dei rettangoli.

Infine, viene applicato l’algoritmo costruttivo alla configurazione attuale dei

blocchi, in seguito alla riduzione.

78

void insertion(int i, int k, int l, int x1, int y1, int x2,

int y2)

La funzione si occupa di inserire un blocco formato da pezzi di indice i e

composto da k colonne e l righe nella posizione indicata. Per far cio, oltre

a inserire il nuovo blocco, vengono rimossi tutti i blocchi e i rettangoli esi-

stenti che si sovrappongono al nuovo blocco e vengono inseriti anche i nuovi

rettangoli che si formano di conseguenza. Alla termine di tutto cio, vengono

eseguiti un’ordinamento sui pezzi e una fusione sui rettangoli. Per finire, si

esegue l’algoritmo costruttivo sulla nuova configurazione.

void valutaMossa()

La funzione valuta la mossa appena effettuata prendendo in considerazione

la funzione obiettivo primaria, quella secondaria, la tabu list, la diversifica-

zione, l’intensificazione e il criterio di aspirazione. Ovvero, viene valutata

positivamente solo la mossa che presenta un valore piu alto della funzione

obiettivo primaria (in caso di parita, conta la funzione obiettivo secondaria)

rispetto alle altre mosse, a meno che tale mossa non sia inserita nella tabu

list, ma se la soluzione ha un valore maggiore della miglior soluzione fino a

quel momento trovata, si ignora la tabu list. Se sono ”attive” le fasi di inten-

sificazione e diversificazione, la funzione obiettivo primaria e modificata dai

relativi parametri. In caso di valutazione positiva, la soluzione trovata con

l’esecuzione della mossa e candidata ad essere la nuova soluzione di partenza

a partire dall’iterazione successiva.

void salvaSoluzioneIniziale(struct pezzo *p, struct

rettangolo *r, struct blocco *b)

Provvede a salvare la nuova soluzione iniziale, sostituendo le liste dei pezzi,

dei rettangoli e dei blocchi della vecchia soluzione iniziale, con quelle nuove

passate come parametro.

79

struct tabu *aggiungiTabu(int v, struct rettangolo *r, struct

tabu *t)

Aggiunge un elemento (formato dal valore della soluzione e dal relativo ret-

tangolo ER) in coda alla tabu list.

struct tabu *rimuoviTabu(struct tabu *t)

Rimuove il primo elemento della tabu list in modo da gestirla come una coda

e rispettare la politica di tipo FIFO.

struct blocco *unisciBlocchi(struct blocco *b)

Provvede ad unire i blocchi formati da insiemi degli stessi pezzi se tali blocchi

sono adiacenti e con un lato in comune, oppure se possono essere congiunti

mediante un semplice spostamento.

struct rettangolo *creaER(struct rettangolo *r)

Restituisce il rettangolo di dimensioni minime, in grado di racchiudere al suo

interno tutti i rettangoli vuoti presenti nella lista passata come parametro.

struct elite *aggiungiSoluzioneElite(struct blocco *b, int

valore, struct elite *e)

Aggiunge alle soluzioni di elite quella formata dalla lista di blocchi e dal rela-

tivo valore totale, passati come parametri. Le soluzioni di elite sono ordinate

per valore della soluzione crescente.

struct elite *sostituisciSoluzioneElite(struct blocco *b, int

valore, struct elite *e)

Se la soluzione passata come parametro e migliore di quella in testa alla lista

delle soluzioni di elite (quella con valore minore), quest’ultima viene sosti-

tuita.

80

void restartingL(struct rettangolo *r)

Esegue una simmetria verticale e, contemporaneamente, una orizzontale sul-

la lista dei rettangoli vuoti.

void restartingC(struct blocco *b)

Esegue una simmetria verticale e, contemporaneamente, una orizzontale sul-

la lista dei blocchi.

double secondaryObjective(struct pezzo *p, struct rettangolo *r)

Calcola il valore della funzione obiettivo secondaria, a partire dalla lista dei

pezzi ancora da tagliare e da quella dei rettangoli vuoti.

int isTabu(struct tabu *t, int valore, struct rettangolo *ER)

Restituisce ”1” se il valore della soluzione e il rettangolo ER passati come

parametro coincidono con un elemento della tabu list, ”0” altrimenti.

int tabuMax(struct pezzo *p)

Calcola, in maniera casuale, la dimensione massima della tabu list in modo

che risulti compresa tra il 25% e il 75% di M , dove M =∑

i Qi.

struct rettangolo *bestRettangolo(int l, int w, struct

rettangolo *r)

Restituisce il rettangolo che ha la piu alta sovrapposizione, in termini di su-

perficie, con il blocco da inserire di dimensioni (l, w).

struct frequenza *inizializzaFrequenza(struct pezzo *p)

Crea una lista di frequenza con tanti elementi (tutti inizializzati a 0) quanti

81

quelli della lista di pezzi passata come parametro.

struct frequenza *aggiornaFrequenza(struct blocco *b, struct

frequenza *f)

Aggiorna le frequenze dei pezzi aggiungendo quante volte ogni singolo pezzo

compare nella lista dei blocchi passata come parametro.

double valoreFrequenza(struct blocco *b, struct frequenza *f,

int num)

Calcola il valore totale delle frequenze dei pezzi che compaiono nella lista dei

blocchi passata come parametro, diviso per il numero di occorrenze num.

struct tabu *modificaDimensioniTabuList(struct tabu *t)

Provvede a generare a caso la nuova dimensione della tabu list e, se questa e

inferiore alla precedente, rimuove gli elementi in esubero.

6.7 Funzioni comuni ai due algoritmi

int constructive()

La funzione constructive e la funzione che realizza l’algoritmo costruttivo.

Per prima cosa viene ordinata la lista dei pezzi in base ai gia citati criteri

(ordinaPezzi). A questo punto, finche esiste un pezzo in grado di entrare

in uno qualunque dei rettangoli vuoti si procede selezionando il pezzo da

tagliare, scandendo la lista ordinata, e scegliendo il rettangolo vuoto di area

minima. Se il pezzo non puo entrare nel rettangolo (fill), si procede sele-

zionando il pezzo successivo. Se il pezzo, invece, puo essere contenuto nel

rettangolo, si procede individuando in maniera casuale, sia il numero di co-

82

lonne, che il numero di righe del pezzo che formeranno il blocco da tagliare,

rispettando pero il limite Qi.

Quindi, si prosegue scegliendo l’angolo del rettangolo vuoto in cui inserire

il blocco, per la precisione si sceglie quello con distanza (distanza) minima

dal corrispettivo angolo del rettangolo base (distanzaMin). Fatto questo

si procede con la rimozione del rettangolo utilizzato (rimuoviRettangolo),

con l’inserimento del blocco nella lista dei blocchi (aggiungiBlocco), con

l’inserimento di uno o piu rettangoli vuoti (aggiungiRettangolo) e con il

modificare i parametri Pi e Qi del pezzo (se Qi = 0, il pezzo viene rimosso con

rimuoviPezzo). Infine si procede con l’eseguire tutte le fusioni che migliorano

l’inserimento dei pezzi (merge).

Quando si giunge in coda alla lista dei pezzi, si seleziona il rettangolo

successivo e ci si riposiziona in testa alla lista dei pezzi. Quando non ci sono

piu pezzi in grado di essere tagliati, la procedura termina restituendo il valore

della soluzione trovata.

void visualizzaP(struct pezzo *p)

Visualizza la lista dei pezzi P , passata come parametro, stampandola a video

nella forma P = {i1, i2, . . . , in}.

void visualizzaL(struct rettangolo *r)

Visualizza la lista dei rettangoli L, passata come parametro, stampandola a

video nella forma L = {(x11, y11, x21, y21), . . . , (x1n, y1n, x2n, y2n)}.

void visualizzaC(struct blocco *b)

Visualizza la lista dei blocchi C, passata come parametro, stampandola a vi-

deo nella forma C = {i1 (x11, y11, x21, y21), . . . , in (x1n, y1n, x2n, y2n)}.

struct pezzo *ordinaPezzi(struct pezzo *p)

Restituisce la lista dei pezzi P , ordinandola secondo i tre criteri espressi nella

83

sezione 2.1.1. Per far cio utilizza le tre funzioni che seguono.

struct pezzo *ordinaPezziPerPi(struct pezzo *p)

Restituisce la lista dei pezzi P , ordinandola per Pi ∗ li ∗ wi decrescenti.

struct pezzo *ordinaPezziPerEi(struct pezzo *p)

Restituisce la lista dei pezzi P , ordinandola per ei decrescenti.

struct pezzo *ordinaPezziPerVi(struct pezzo *p)

Restituisce la lista dei pezzi P , ordinandola per vi decrescenti.

struct pezzo *aggiungiPezzo(int i, int l, int w, int P, int Q,

int v, struct pezzo *p)

Aggiunge un pezzo in coda alla lista dei pezzi passata come parametro. Se

l’indice i del pezzo esiste gia, vengono modificati solo il lower bound Pi e

l’upper bound Qi.

struct rettangolo *aggiungiRettangolo(int x1, int y1, int x2,

int y2, struct rettangolo *r)

Aggiunge un rettangolo alla lista dei rettangoli passata come parametro, or-

dinata per area crescente, mantenendone l’ordinamento.

struct blocco *aggiungiBlocco(int i, int x1, int y1, int x2,

int y2, int k, int l, int v, struct blocco *b)

Aggiunge un blocco in coda alla lista di blocchi passata come parametro.

struct pezzo *rimuoviPezzo(int i, struct pezzo *p)

Rimuove, liberando la memoria, il pezzo (se esiste) di indice i presente nella

84

lista p.

struct rettangolo *rimuoviRettangolo(int x1, int y1, int x2,

int y2, struct rettangolo *r)

Rimuove, liberando la memoria, il rettangolo (se esiste) di coordinate (x1, y1)

e (x2, y2), presente nella lista r.

struct blocco *rimuoviBlocco(int x1, int y1, int x2, int y2,

struct blocco b*)

Rimuove, liberando la memoria, il blocco (se esiste) di coordinate (x1, y1) e

(x2, y2), presente nella lista b.

void eliminaPezzi(struct pezzo *p)

Rimuove, liberando la memoria, tutti i pezzi presenti nella lista passata come

parametro.

void eliminaRettangoli(struct rettangolo *r)

Rimuove, liberando la memoria, tutti i rettangoli presenti nella lista passata

come parametro.

void eliminaBlocchi(struct blocco *b)

Rimuove, liberando la memoria, tutti i blocchi presenti nella lista passata

come parametro.

int contaPezzi(struct pezzo p*)

Restituisce il numero di pezzi presenti nella lista p.

int contaRettangoli(struct rettangolo *r)

85

Restituisce il numero di rettangoli presenti nella lista r.

int contaBlocchi(struct blocco *b)

Restituisce il numero di blocchi presenti nella lista b.

struct pezzo *selezionaPezzo(int i, struct pezzo *p)

Seleziona il pezzo di indice i e ne restituisce il riferimento.

struct rettangolo *selezionaRettangolo(int i, struct

rettangolo *r)

Seleziona il rettangolo di indice i e ne restituisce il riferimento.

struct blocco *selezionaBlocco(int i, struct blocco *b)

Seleziona il blocco di indice i e ne restituisce il riferimento.

struct pezzo *copiaPezzi(struct pezzo *p)

Restituisce una copia dell’intera lista dei pezzi passata come parametro.

struct rettangolo *copiaRettangoli(struct rettangolo *r)

Restituisce una copia dell’intera lista dei rettangoli passata come parametro.

struct blocco *copiaBlocchi(struct blocco *b)

Restituisce una copia dell’intera lista dei blocchi passata come parametro.

struct rettangolo *merge(struct rettangolo *r, struct pezzo *p)

Valuta ed esegue tutte le fusioni tra i rettangoli vuoti adiacenti utilizzando

i criteri espressi nella sezione 2.1.4, e per far cio necessita, oltre che della

lista dei rettangoli da fondere, anche di quella dei pezzi ancora da tagliare

86

ordinata secondo il criterio migliore.

double distanza(double x1, double y1, double x2, double y2)

Restituisce la distanza, in valore assoluto, tra due punti (x1, y1) e (x2, y2).

double distanzaMin(double bl, double br, double tl, double tr)

Restituisce la distanza minima tra le quattro passate come parametro che

corrispondono alle distanze tra i quattro angoli di un rettangolo vuoto e i

corrispettivi angoli del rettangolo base (bottom-left, bottom-right, top-left e

top-right).

int isAdjacent(struct blocco *b, struct rettangolo *r)

Restituisce ”1” se il blocco e il rettangolo passati come parametro sono adia-

centi, ”0” altrimenti.

int upperBound(struct pezzo *p)

Calcola l’upper bound del problema a partire dalla lista dei pezzi da tagliare.

int isUnweighted(struct pezzo *p)

Se per ogni pezzo i risulta ei = 1, restituisce ”1” (problema di tipo un-

weighted), altrimenti restituisce ”0”.

int isFeasible(struct pezzo *p)

Se per ogni pezzo i ancora da tagliare risulta Pi = 0, restituisce ”1” (solu-

zione ammissibile), altrimenti restituisce ”0” (soluzione non ammissibile).

int fill(struct rettangolo *r, struct pezzo *p)

Restituisce ”1” se il pezzo e in grado di essere ”tagliato” dal rettangolo vuo-

87

to, ”0” altrimenti.

double eMax(struct pezzo *p)

Restituisce la piu grande tra tutte le efficienze e dei pezzi.

void decreasingQi(struct pezzo *p)

Decrementa gli upper bound Qi dei pezzi della lista passata come parametro,

seguendo le formule indicate nella sezione 4.3.2. Quindi, se un pezzo presenta

un Qi = 0, questo viene rimosso dalla lista dei pezzi ancora da tagliare.

void apriFile()

Richiede il nome del file in cui sono contenuti i problemi da risolvere e prov-

vede ad aprirlo in lettura. Inoltre apre in scrittura il file ”risultati.txt” in cui

verranno scritte le soluzioni dei problemi e i tempi computazionali.

void chiudiFile()

Provvede a chiudere i file precedentemente aperti.

void caricaDatiProblema()

Crea le tre liste P , L e C inizializzandole con i valori presenti sul file.

void inizializzaParametri()

Inizializza tutti i parametri fondamentali come il numero di iterazioni numI-

ter, il numero massimo di iterazioni maxIter, ecc.

void salvaSoluzioneMigliore(struct pezzo *p, struct

rettangolo *r, struct blocco *b)

Provvede a salvare le tre liste associate alla soluzione migliore trovata fino a

88

quel momento.

void eliminaListe()

Si occupa di eliminare tutte le liste precedentemente create in modo da libe-

rare la memoria occupata.

void visualizzaRisultati()

Stampa a video la soluzione trovata e salva il risultato e il tempo impiegato

all’interno del file ”risultati.txt”.

89

Capitolo 7

Test e risultati computazionali

7.1 Tipi di test

Per testare entrambi gli algoritmi sono stati utilizzati diversi insiemi di

problemi:

1. Un insieme di 21 problemi estratti dalla letteratura: dodici da Bea-

sley [6], due da Hadjiconstantinou e Christofides [8], uno da Wang [21],

uno da Christofides e Whitlock [22], cinque da Fekete e Schepers [9].

Le soluzioni ottime sono tutte note. I problemi sono stati risolti anche

da Beasley [15].

2. Un insieme di 630 problemi di grosse dimensioni generati da Bea-

sley [15], seguendo il lavoro di Fekete e Schepers [9]. Tutti i problemi

hanno un rettangolo base di dimensioni (100, 100). Per ogni valo-

re di m, il numero di tipi diversi di pezzi (m=40, 50, 100, 150, 250,

500, 1000), 10 problemi sono generati in maniera casuale con Pi = 0,

Qi = Q∗, ∀i = 1, ...,m, dove Q∗=1; 3; 4. Queste 630 istanze sono

suddivise in tre tipi di problemi, a seconda della percentuale di tipi di

pezzi di ogni classe:

91

Classe Descrizione Lunghezza Larghezza

1 Corti e larghi [1,50] [75,100]

2 Lunghi e stretti [75,100] [1,50]

3 Grandi [50,100] [50,100]

4 Piccoli [1,50] [1,50]

Tipo % di classe 1 % di classe 2 % di classe 3 % di classe 4

1 20 20 20 40

2 15 15 15 55

3 10 10 10 70

Il valore assegnato ad ogni pezzo e uguale alla sua area moltiplicata

per un intero scelto a caso tra {1, 2, 3}.

3. I 21 problemi provenienti dalla letteratura sono stati trasformati da

Beasley [15] in doubly constrained problems modificando alcuni lower

bounds Pi, in particolare per ogni tipo di pezzo da i = 1, ...,m che

soddisfi∑m

j=1,j 6=i(ljwj)Pj + liwi ≤ (LW )/3, il lower bound Pi e posto

uguale a 1.

Questo insieme di problemi permette di verificare la bonta degli algo-

ritmi nel caso generale di problemi di tipo doubly constrained.

4. Infine sono stati eseguiti i test utilizzando i problemi usati da Leung

et al [14], che consistono in tre istanze da Lai e Chan [11], cinque da

Jakobs [23] e due da Leung et al [14]. I test sono stati eseguiti anche

su 21 istanze di grosse dimensioni create da Hopper e Turton [25].

Questi problemi sono tutti di tipo unweighted in cui il valore di ogni

pezzo corrisponde alla sua area e l’obbiettivo da raggiungere e ridurre

al minimo ”lo scarto” nel rettangolo base.

Quest’ultima classe di problemi ha caratteristiche complementari rispetto

agli altri tipi. Infatti i primi tre tipi di problemi possono essere considerati

una sorta di problemi di selezione perche ci sono molti pezzi disponibili e solo

92

una piccola parte di essi dovra essere inclusa nella soluzione ottimale. Invece

i problemi del quarto tipo possono essere definiti come problemi a incastro

in cui tutti i pezzi disponibili devono far parte della soluzione ottima e la

difficolta sta nel trovare la loro corretta posizione per far sı che entrino tutti

nel rettangolo base. Un algoritmo che funzioni bene con entrambi i tipi di

problemi puo essere considerato un buon general purpose algorithm.

7.2 Risultati computazionali

I risultati computazionali completi appaiono nelle Tabelle dalla 1 alla 4, per

quanto riguarda l’algoritmo GRASP, e dalla 5 alla 8, per quanto riguarda

l’algoritmo Tabu Search. Nelle tabelle e stato fatto un confronto con l’al-

goritmo GRASP sviluppato da Alvarez-Valdes et al [1] e con quello Tabu

Search realizzato sempre da Alvarez-Valdes et al [2]. Il confronto avviene sia

sui risultati ottenuti dalle due procedure, sia sui tempi impiegati a risolvere

i problemi.

I tempi di elaborazione, pero, non possono essere direttamente confron-

tati in quanto entrambi i miei algoritmi sono stati sviluppati in C ed eseguiti

su un AMD Athlon 2100+, mentre gli algoritmi di Alvarez-Valdes et al sono

stati implementati utilizzando il linguaggio C++ ed eseguiti su un Pentium

III 800 MHz. Tuttavia e possibile affermare, con un certo margine di appros-

simazione, che il PC usato da me per l’esecuzione dei test e circa due volte

piu veloce rispetto a quello usato da Alvarez-Valdes et al.

7.2.1 Confronto tra gli algoritmi GRASP

Confrontando il primo insieme di problemi (Tabella 1), quelli provenienti

dalla letteratura, si nota come il mio algoritmo produca risultati identici a

quelli di Alvarez-Valdes et al, sia in termini di numero di soluzioni ottime

trovate, sia in termini di valore di tali soluzioni, risolvendo i problemi in circa

meta tempo, in accordo con le differenti prestazioni dei processori.

Per quanto riguarda il secondo insieme di problemi (Tabella 2), per i quali

93

esistono solo risultati aggregati, l’algoritmo di Alvarez-Valdes et al produce

risultati leggermente migliori in un tempo di poco superiore. Per questo tipo

di problemi, non essendo disponibili le soluzioni ottime, il confronto e stato

fatto prendendo in considerazione i relativi upper bound.

Il terzo insieme e rappresentato dai problemi doubly constrained (Tabel-

la 3) nei quali come soluzione ottima di riferimento e stata scelta la stessa

dei problemi constrained, poiche quella reale non era disponibile. Per questi

problemi i risultati del mio algoritmo sono migliori, sia dal punto di vista del

valore delle soluzioni trovate, che dal punto di vista dei tempi (inferiori alla

meta). Alcuni problemi sono ”non risolvibili” (not feasible), ciononostante

sono stati inseriti lo stesso nella tabella per dimostrare che l’algoritmo e in

grado di riconoscerli quando li incontra.

Infine, confrontando i risultati prodotti sui problemi di tipo unweighted

(Tabella 4) possiamo notare come il mio algoritmo fornisca risultati decisa-

mente migliori, rispetto a quelli dell’algoritmo di Alvarez-Valdes et al, sotto

tutti i punti di vista, cioe del numero di soluzioni ottime trovate, dei valori

delle soluzioni e dei tempi di esecuzione (inferiori a un terzo).

La Figura 16 riassume il confronto tra i due algoritmi GRASP.

Figura 16 Confronto tra gli algoritmi GRASP

94

Dimensioni CPU (s)

I (L,W) m M Ciani Alvarez Ottimo Ciani Alvarez

1 (10,10) 5 10 164 164 164 0,00 0,00

2 (10,10) 7 17 230 230 230 0,00 0,00

3 (10,10) 10 21 247 247 247 0,00 0,00

4 (15,10) 5 7 268 268 268 0,00 0,00

5 (15,10) 7 14 358 358 358 0,00 0,00

6 (15,10) 10 15 289 289 289 0,00 0,00

7 (20,20) 5 8 430 430 430 0,00 0,00

8 (20,20) 7 13 834 834 834 0,06 0,77

9 (20,20) 10 18 924 924 924 0,14 0,00

10 (30,30) 5 13 1452 1452 1452 0,04 0,00

11 (30,30) 7 15 1688 1688 1688 0,06 0,05

12 (30,30) 10 22 1865 1865 1865 0,10 0,05

13 (30,30) 7 7 1178 1178 1178 0,04 0,00

14 (30,30) 15 15 1270 1270 1270 0,05 0,00

15 (70,40) 19 42 2726 2726 2726 0,13 0,77

16 (40,70) 20 62 1860 1860 1860 0,11 0,39

17 (100,100) 15 50 27589 27589 27718 0,33 2,31

18 (100,100) 30 30 21976 21976 22502 1,11 4,17

19 (100,100) 30 30 23743 23743 24019 1,30 3,68

20 (100,100) 33 61 32893 32893 32893 0,90 0,00

21 (100,100) 29 97 27923 27923 27923 0,43 0,00

Deviazione media dall’ottimo 0,19% 0,19% 0,23 0,58

Numero di soluzioni ottime 18 18

Tabella 1 GRASP - Problemi dalla letteratura

95

Deviazione media percentuale dall’UB CPU (s)

m Q∗ M Ciani Alvarez Ciani Alvarez

40 1 40 7,01 6,97 1,83 2,33

3 120 2,39 2,22 5,40 6,62

4 160 1,98 1,81 3,33 4,44

50 1 50 4,91 4,80 3,20 4,71

3 150 1,79 1,50 5,48 7,05

4 200 1,37 1,18 4,38 5,34

100 1 100 1,59 1,51 4,11 5,36

3 300 0,74 0,47 7,43 9,41

4 400 0,45 0,26 5,26 6,99

150 1 150 0,93 0,89 4,90 5,53

3 450 0,27 0,14 9,76 11,71

4 600 0,26 0,11 4,77 6,75

250 1 250 0,51 0,51 4,78 5,27

3 750 0,16 0,04 10,76 13,89

4 1000 0,10 0,03 9,91 6,65

500 1 500 0,12 0,05 3,02 3,24

3 1500 0,02 0,00 8,33 12,24

4 2000 0,02 0,00 4,32 1,15

1000 1 1000 0,01 0,00 4,76 1,01

3 3000 0,00 0,00 5,92 6,53

4 4000 0,00 0,00 4,39 0,29

Tipo 1 1,13 1,04 4,44 5,13

Tipo 2 1,23 1,14 5,42 5,90

Tipo 3 1,16 1,03 6,00 7,28

Tutti 1,17 1,07 5,29 6,02

Tabella 2 GRASP - Problemi random di grosse dimensioni

96

Dimensioni CPU (s)

I (L,W) m M Ciani Alvarez Ottimo Ciani Alvarez

1 (10,10) 5 10 164 164 164 0,04 0,00

2 (10,10) 7 17 225 225 230 0,04 0,00

3 (10,10) 10 21 220 220 247 0,04 0,00

4 (15,10) 5 7 268 268 268 0,04 0,00

5 (15,10) 7 14 301 301 358 0,04 0,00

6 (15,10) 10 15 265 252 289 0,08 0,00

7 (20,20) 5 8 430 430 430 0,00 0,00

8 (20,20) 7 13 819 819 834 0,06 0,77

9 (20,20) 10 18 924 924 924 0,14 0,00

10 (30,30) 5 13 n/f n/f n/f 0,04 0,00

11 (30,30) 7 15 1518 1518 1688 0,06 0,05

12 (30,30) 10 22 1666 1648 1865 0,10 0,05

13 (30,30) 7 7 1178 1178 1178 0,04 0,00

14 (30,30) 15 15 1216 1216 1270 0,05 0,00

15 (70,40) 19 42 2716 2700 2726 0,13 0,77

16 (40,70) 20 62 1720 1720 1860 0,11 0,39

17 (100,100) 15 50 24342 24869 27718 0,33 2,31

18 (100,100) 30 30 19526 19083 22502 1,11 4,17

19 (100,100) 30 30 n/f n/f n/f 1,30 3,68

20 (100,100) 33 61 28042 27898 32893 0,90 0,00

21 (100,100) 29 97 22011 22011 27923 0,43 0,00

Deviazione media dall’ottimo 7,02% 7,36% 0,50 1,29

n/f: Nessuna soluzione fattibile e stata trovata

Tabella 3 GRASP - Problemi di tipo doubly constrained

97

Dimensioni CPU (s)

I (L,W) m M Ciani Alvarez Ottimo Ciani Alvarez

1 (400,200) 9 10 80000 80000 80000 0,00 0,00

2 (400,200) 7 15 79000 79000 79000 0,00 0,00

3 (400,400) 5 20 160000 154600 160000 0,00 4,12

4 (70,80) 14 20 5375 5447 5600 4,06 10,16

5 (70,80) 16 25 5436 5455 5600 6,49 15,44

6 (120,45) 22 25 5328 5328 5400 4,94 12,57

7 (90,45) 16 30 4050 3978 4050 0,05 10,28

8 (65,45) 18 30 2853 2871 2925 6,72 14,94

9 (150,110) 40 40 15888 15856 16500 33,90 90,52

10 (160,120) 50 50 18600 18628 19200 61,50 132,26

11 (20,20) 16 16 400 400 400 0,31 0,94

12 (20,20) 17 17 386 386 400 4,92 9,28

13 (20,20) 16 16 400 400 400 0,04 0,06

14 (40,15) 25 25 594 590 600 8,43 19,44

15 (40,15) 25 25 596 597 600 7,33 17,36

16 (40,15) 25 25 600 600 600 7,88 0,71

17 (60,30) 28 28 1788 1765 1800 14,66 26,80

18 (60,30) 29 29 1760 1755 1800 21,12 37,35

19 (60,30) 28 28 1785 1774 1800 18,91 30,92

20 (60,60) 49 49 3509 3528 3600 44,56 102,05

21 (60,60) 49 49 3519 3524 3600 49,01 110,79

22 (60,60) 49 49 3565 3544 3600 51,72 94,41

23 (60,90) 73 73 5322 5308 5400 89,97 212,07

24 (60,90) 73 73 5312 5313 5400 103,48 231,56

25 (60,90) 73 73 5325 5312 5400 98,91 231,24

26 (80,120) 97 97 9464 9470 9600 221,40 480,44

27 (80,120) 97 97 9504 9453 9600 205,05 465,49

28 (80,120) 97 97 9481 9450 9600 238,54 478,02

29 (160,240) 196 196 37652 37661 38400 1355,76 3760,14

30 (160,240) 197 197 37958 37939 38400 1089,09 2841,96

31 (160,240) 196 196 37721 37745 38400 1354,49 3700,99

Deviazione media dall’ottimo 1,46% 1,68% 164,62 423,95

Numero di soluzioni ottime 7 5

Tabella 4 GRASP - Problemi di tipo unweighted

98

7.2.2 Confronto tra gli algoritmi Tabu Search

Se si confrontano i 21 problemi provenienti dalla letteratura (Tabella 5), i due

algoritmi Tabu Search producono esattamente gli stessi risultati, trovando

tutte e 21 le soluzioni ottime; ma il mio algoritmo li risolve in meta tempo.

I problemi di grosse dimensioni (Tabella 6), invece, presentano risul-

tati aggregati pressoche identici, con qualche differenza solo da categoria

a categoria. Anche i tempi computazionali sono paragonabili, tenendo in

considerazione le diversita dei processori.

Per quanto riguarda i problemi doubly constrained (Tabella 7), anche

in questo caso i risultati sono identici e i tempi sono paragonabili. Proba-

bilmente, anche se non vi e certezza, poiche le soluzioni ottime di questo

tipo di problema non sono disponibili, entrambi gli algoritmi sono riusciti a

individuare tutte le soluzioni ottime.

Infine, i problemi di unweighted (Tabella 8) vengono risolti dai due algo-

ritmi realizzando risultati totali molto simili, ma piuttosto diversi da istanza

a istanza. Per quanto riguarda i tempi di esecuzione, anche questa volta, il

mio algoritmo risolve i problemi in un tempo inferiore (circa un terzo).

La Figura 17 mostra il paragone tra i due algoritmi Tabu Search.

Figura 17 Confronto tra gli algoritmi Tabu Search

99

Dimensioni CPU (s)

I (L,W) m M Ciani Alvarez Ottimo Ciani Alvarez

1 (10,10) 5 10 164 164 164 0,00 0,06

2 (10,10) 7 17 230 230 230 0,00 0,00

3 (10,10) 10 21 247 247 247 0,00 0,00

4 (15,10) 5 7 268 268 268 0,00 0,00

5 (15,10) 7 14 358 358 358 0,00 0,00

6 (15,10) 10 15 289 289 289 0,00 0,00

7 (20,20) 5 8 430 430 430 0,00 0,00

8 (20,20) 7 13 834 834 834 0,06 0,16

9 (20,20) 10 18 924 924 924 0,00 0,05

10 (30,30) 5 13 1452 1452 1452 0,00 0,00

11 (30,30) 7 15 1688 1688 1688 0,00 0,00

12 (30,30) 10 22 1865 1865 1865 0,03 0,06

13 (30,30) 7 7 1178 1178 1178 0,00 0,00

14 (30,30) 15 15 1270 1270 1270 0,00 0,00

15 (70,40) 19 42 2726 2726 2726 0,04 0,11

16 (40,70) 20 62 1860 1860 1860 0,03 0,06

17 (100,100) 15 50 27718 27718 27718 0,00 0,05

18 (100,100) 30 30 22502 22502 22502 1,09 2,14

19 (100,100) 30 30 24019 24019 24019 1,51 3,40

20 (100,100) 33 61 32893 32893 32893 0,41 0,66

21 (100,100) 29 97 27923 27923 27923 0,00 0,00

Deviazione media dall’ottimo 0,00% 0,00% 0,15 0,32

Numero di soluzioni ottime 21 21

Tabella 5 Tabu Search - Problemi dalla letteratura

100

Deviazione media percentuale dall’UB CPU (s)

m Q∗ M Ciani Alvarez Ciani Alvarez

40 1 40 6,41 6,55 6,99 10,97

3 120 1,93 1,95 8,84 14,20

4 160 1,52 1,65 11,37 18,26

50 1 50 4,56 4,85 9,86 15,49

3 150 1,28 1,27 15,42 22,50

4 200 0,89 0,96 12,25 18,19

100 1 100 1,59 1,50 19,95 38,79

3 300 0,38 0,31 18,34 32,11

4 400 0,14 0,18 12,29 19,67

150 1 150 1,02 0,84 35,31 54,90

3 450 0,15 0,07 20,73 31,76

4 600 0,06 0,05 14,49 19,87

250 1 250 0,69 0,45 72,05 90,07

3 750 0,02 0,01 8,78 13,70

4 1000 0,00 0,00 3,06 4,50

500 1 500 0,05 0,03 67,93 86,17

3 1500 0,00 0,00 0,88 1,10

4 2000 0,00 0,00 0,59 0,84

1000 1 1000 0,00 0,00 5,16 7,80

3 3000 0,00 0,00 0,91 1,54

4 4000 0,00 0,00 0,76 1,19

Tipo 1 0,96 0,95 12,75 19,61

Tipo 2 1,06 1,06 15,49 23,84

Tipo 3 0,95 0,94 21,17 32,56

Tutti 0,99 0,98 16,47 25,34

Tabella 6 Tabu Search - Problemi random di grosse dimensioni

101

Dimensioni CPU (s)

I (L,W) m M Ciani Alvarez Ottimo Ciani Alvarez

1 (10,10) 5 10 164 164 164 0,00 0,00

2 (10,10) 7 17 225 225 230 0,81 1,70

3 (10,10) 10 21 220 220 247 1,04 2,26

4 (15,10) 5 7 268 268 268 0,00 0,00

5 (15,10) 7 14 301 301 358 0,82 1,48

6 (15,10) 10 15 265 265 289 0,78 1,59

7 (20,20) 5 8 430 430 430 0,00 0,00

8 (20,20) 7 13 819 819 834 0,96 1,76

9 (20,20) 10 18 924 924 924 0,00 0,00

10 (30,30) 5 13 n/f n/f n/f 0,55 0,94

11 (30,30) 7 15 1518 1518 1688 1,06 2,52

12 (30,30) 10 22 1672 1672 1865 2,10 3,73

13 (30,30) 7 7 1178 1178 1178 0,00 0,00

14 (30,30) 15 15 1216 1216 1270 2,05 3,18

15 (70,40) 19 42 2716 2716 2726 3,18 6,16

16 (40,70) 20 62 1720 1720 1860 3,19 5,27

17 (100,100) 15 50 25384 25384 27718 10,32 25,27

18 (100,100) 30 30 19657 19657 22502 8,17 18,35

19 (100,100) 30 30 n/f n/f n/f 7,37 12,41

20 (100,100) 33 61 28974 28974 32893 21,91 37,46

21 (100,100) 29 97 22011 22011 27923 35,63 61,90

Deviazione media dall’ottimo 6,62% 6,62% 4,76 8,86

n/f: Nessuna soluzione fattibile e stata trovata

Tabella 7 Tabu Search - Problemi di tipo doubly constrained

102

Dimensioni CPU (s)

I (L,W) m M Ciani Alvarez Ottimo Ciani Alvarez

1 (400,200) 9 10 80000 80000 80000 0,00 0,00

2 (400,200) 7 15 79000 79000 79000 0,00 0,02

3 (400,400) 5 20 160000 160000 160000 0,04 0,38

4 (70,80) 14 20 5600 5600 5600 0,51 1,89

5 (70,80) 16 25 5464 5540 5600 3,42 16,88

6 (120,45) 22 25 5400 5400 5400 0,20 0,42

7 (90,45) 16 30 4050 4050 4050 2,04 1,97

8 (65,45) 18 30 2925 2925 2925 2,51 1,53

9 (150,110) 40 40 16212 16280 16500 17,91 52,36

10 (160,120) 50 50 19056 19044 19200 19,93 63,95

11 (20,20) 16 16 400 400 400 0,13 0,42

12 (20,20) 17 17 400 400 400 3,86 4,23

13 (20,20) 16 16 400 400 400 0,19 0,95

14 (40,15) 25 25 600 600 600 0,11 0,44

15 (40,15) 25 25 600 600 600 1,95 4,16

16 (40,15) 25 25 600 600 600 0,00 0,00

17 (60,30) 28 28 1800 1800 1800 3,03 4,91

18 (60,30) 29 29 1800 1800 1800 4,52 10,11

19 (60,30) 28 28 1800 1800 1800 1,47 5,52

20 (60,60) 49 49 3568 3580 3600 14,82 45,27

21 (60,60) 49 49 3564 3564 3600 17,93 68,59

22 (60,60) 49 49 3576 3580 3600 15,05 51,11

23 (60,90) 73 73 5364 5342 5400 35,88 135,97

24 (60,90) 73 73 5346 5361 5400 29,14 96,80

25 (60,90) 73 73 5364 5375 5400 27,23 82,06

26 (80,120) 97 97 9551 9548 9600 65,66 240,39

27 (80,120) 97 97 9540 9448 9600 103,73 399,86

28 (80,120) 97 97 9540 9565 9600 56,98 206,78

29 (160,240) 196 196 38025 38026 38400 708,45 3054,38

30 (160,240) 197 197 38091 38145 38400 533,71 1990,70

31 (160,240) 196 196 37985 37867 38400 1604,39 5615,75

Deviazione media dall’ottimo 0,46% 0,42% 107,96 392,19

Numero di soluzioni ottime 16 16

Tabella 8 Tabu Search - Problemi di tipo unweighted

103

7.2.3 Confronto tra le due tecniche

Sebbene la tecnica GRASP produca un algoritmo eccezionale sotto il profilo

dei risultati, dimostrando di essere una delle euristiche che meglio si adatta al

problema di taglio bidimensionale, la tecnica Tabu Search si rivela addirittura

superiore permettendo di realizzare un algoritmo in grado di risolvere tutte le

categorie di problemi presentate in precedenza, con risultati migliori ovunque

(come si vede dalla Figura 18).

Per quanto riguarda i tempi di esecuzione, invece, i due algoritmi presen-

tano risultati diversi. I problemi provenienti dalla letteratura, sia nel caso

constrained, sia nel caso doubly constrained, vengono risolti dai due algoritmi

in tempi piu o meno uguali (con una leggera preferenza per l’algoritmo Tabu

Search). I risultati dei problemi di grosse dimensioni e di quelli unweighted,

invece, sono ottenuti dall’algoritmo GRASP in tempi notevolmente inferiori.

Per questo motivo e possibile concludere che, qualora il maggior tem-

po impiegato non sia un vincolo strettissimo, e preferibile impiegare sem-

pre l’algoritmo Tabu Search. Qualora invece, il tempo sia il vincolo fon-

damentale, per problemi complessi e meglio utilizzare l’algoritmo GRASP,

accontentandosi di risultati leggermente peggiori.

Figura 18 Confronto tra le due tecniche

104

Conclusioni

In questa tesi sono stati sviluppati due algoritmi euristici, uno basato sul-

la tecnica GRASP e uno basato sulla tecnica Tabu Search. Entrambi gli

algoritmi risolvono problemi di taglio bidimensionale non a ghigliottina.

L’algoritmo GRASP e costituito da due fasi: una costruttiva e una di

miglioramento. Nella prima fase l’algoritmo seleziona i pezzi da tagliare, in

maniera casuale, utilizzando una procedura di tipo greedy. Nella seconda

fase, invece, vengono modificate alcune decisioni prese nella fase costruttiva,

permettendo all’intera procedura di migliorare la soluzione trovata. Durante

le varie iterazioni vengono eseguite procedure per diminuire la complessita

del problema e per adattare la scelta dei pezzi ai risultati fino a quel momento

ottenuti.

Per quanto riguarda l’algoritmo Tabu Search, sono state proposte due

mosse che si fondano sull’inserimento e sulla riduzione di blocchi di pez-

zi. L’efficienza delle mosse si basa su una strategia di tipo merge and fill

che adatta i rettangoli vuoti ai pezzi ancora da tagliare. Inoltre sono sta-

te sviluppate anche strategie di tabu list dinamica, e di diversificazione e

intensificazione, basate sulla memoria a lungo termine.

I risultati computazionali dimostrano che gli algoritmi producono risultati

ottimi per ogni categoria di problemi: di piccole e di grandi dimensioni,

constrained e doubly constrained, weighted e unweighted. Per questo motivo,

gli algoritmi dimostrano di essere totalmente general purpose e adatti ad ogni

tipo di problema di taglio bidimensionale.

105

Possibili sviluppi futuri

Entrambi gli algoritmi hanno dimostrato di essere altamente competitivi e

pronti per essere utilizzati da una qualunque azienda, che necessiti di un

programma in grado di risolvere il problema di taglio bidimensionale presen-

tato in precedenza. Tuttavia, poiche gli algoritmi sono stati implementati in

maniera totalmente modulare e con codice altamente riusabile, sarebbe pos-

sibile migliorare sia le prestazioni che l’interfaccia in maniera relativamente

semplice. Tra questi miglioramenti e possibile:

1. Utilizzare una tecnica piu efficiente nella fase di miglioramento dell’al-

goritmo GRASP (semplicemente sostituendo la vecchia funzione con

quella nuova).

2. Introdurre nuove mosse nell’algoritmo Tabu Search per giungere piu

velocemente alla soluzione migliore (semplicemente aggiungendole al

corpo principale dell’algoritmo).

3. Applicare una tecnica mista costituita da una prima fase costruttiva in

cui pezzi e rettangoli vuoti vengono scelti in maniera casuale (GRASP),

e da una seconda fase in cui si applicano mosse e strategie tipiche

dell’approccio Tabu Search.

4. Sviluppare un’interfaccia piu potente che permetta, per esempio, di

introdurre i dati dei problemi in diversi modi (e non solo tramite file) o

di visualizzare le soluzioni ottenute, magari con l’utilizzo di componenti

grafici.

107

Bibliografia

[1] Alvarez-Valdes R, Parreno F e Tamarit JM (2005). A GRASP algorithm

for constrained two-dimensional non-guillotine cutting problems. J Opl

Res Soc 56: 414-425.

[2] Alvarez-Valdes R, Parreno F e Tamarit JM (2005). A tabu search al-

gorithm for two-dimensional non-guillotine cutting problems. J Opl Res

Soc: attualmente in stampa.

[3] Tsai RD, Malstrom EM e Meeks HD (1988). A two-dimesional pal-

letizing procedure for warehouse loading operations. IIE Trans 20:

418-425.

[4] Arenales M e Morabito R (1995). An AND/OR-graph approach to the

solution ot two-dimensional non-guillotine cutting problems. Eur J Opl

Res 84: 599-617.

[5] Healy P, Creavin M e Kuusik A (1999). An optimal algorithm for

placement rectangle. Opns Res Lett 24: 73-80.

[6] Beasley JE (1985). An exact two-dimensional non-guillotine cutting tree

search procedure. Opns Res 33: 49-64.

[7] Scheithauer G e Terno J (1993). Modeling of packing problems.

Optimization 28: 63-84.

[8] Hadjiconstantinou E e Christofides N (1995). An exact algorithm for

general, orthogonal, two-dimensional knapsack problems. Eur J Opl Res

83: 39-56.

109

[9] Fekete SP e Schepers J (2004). An exact algorithm for higher-

dimensional orthogonal packing. Il materiale e disponibile all’indirizzo

http://www.math.tu-bs.de/∼fekete

[10] Caprara A e Monaci M (2004). On the two-dimensional Knapsack

problem. Opns Res Lett 32: 5-14.

[11] Lai KK e Chan JWM (1997). Developing a simulated annealing

algorithm for the cutting stock problem. Comput Ind Eng 32: 115-127.

[12] Lai KK e Chan JWM (1997). A evolutionary algorithm for the

rectangular cutting stock problem. Int J Ind Eng 4: 130-139.

[13] Leung TW, Yung CH e Troutt MD (2001). Applications of genetic search

and simulated annealing to the two-dimensional non-guillotine cutting

stock problem. Comput Ind Eng 40: 201-214.

[14] Leung TW, Chan CK e Troutt MD (2003). Application of a mixed si-

mulated annealing-genetic algorithm heuristic for the two-dimensional

orthogonal packing problem. Eur J Opl Res 145: 530-542.

[15] Beasley JE (2004). A population heuristic for constrained two-

dimensional non-guillotine cutting. Eur J Opl Res 156: 601-627.

[16] Feo T e Resende MGC (1989). A probabilistic heuristic for a

computationally difficult set covering problem. Opns Res Lett 8: 67-71.

[17] Resende MGC e Ribeiro CC (2003). Greedy randomized adaptive search

procedures. Handbook of Metaheuristics. Kluwer Academic Publishers,

Dordrecht, MA, pp 219-249.

[18] Prais M e Ribeiro CC (2000). Reactive GRASP: an application to a

matrix decomposition problem in TDMA traffic assignment. INFORMS

J Comput 12: 164-176.

[19] Delorme X, Gandibleux X e Rodriguez J (2003). GRASP for set packing

problems. Eur J Opl Res 153: 564-580.

110

[20] Beltran JC, Celderon JE, Cabrera RJ e Moreno JM (2002). Proce-

dimientos constructivos adaptativos (GRASP) para el problema del

empaquetado bidimensional. Rev Iberoam Intel Artif 15: 26-33.

[21] Wang PY (1983). Two algorithms for constrained two-dimensional

cutting stock problems. Opns Res 31: 573-586.

[22] Christofides N e Whitlock C (1977). An algorithm for two-dimensional

cutting problems. Opns Res 25: 30-44.

[23] Jakobs S (1996). On genetic algorithms for the packing of polygons. Eur

J Opl Res 88: 165-181.

[24] Glover F e Laguna M (1997). Tabu Search. Kluver Academic Publishers,

Boston.

[25] Hopper E e Turton BCH (2001). An empirical investigation of meta-

heuristic and heuristic algorithms for a 2D packing problem. Eur J Opl

Res 128: 34-57.

[26] Ducatelle F e Levine J (2001). Ant Colony Optimization for Bin Pac-

king and Cutting Stock Problems. Proceedings of the UK Workshop on

Computational Intelligence.

111