510 likes | 712 Views
Constraint Programming con ILOG Solver. Modellare e risolvere problemi con CP. Michele Lombardi <michele.lombardi2@unibo.it>. Cosa ci aspetta. Di cosa parleremo. Modellazione e soluzione di problemi con CP Strumenti per modellare (variabili, vincoli) Problematiche di modellazione
E N D
Constraint Programming con ILOG Solver Modellare e risolvere problemi con CP Michele Lombardi <michele.lombardi2@unibo.it>
Cosa ci aspetta Di cosa parleremo... • Modellazione e soluzione di problemi con CP • Strumenti per modellare (variabili, vincoli) • Problematiche di modellazione • Ottimizzazione del modello e del metodo di soluzione • Risolutore: ILOG solver ...e come ne parleremo • Considereremo un unico esempio di riferiento... • ...e TANTE sue varianti ;-) • Ad ogni passo sarà introdotto qualcosa di nuovo... • ...e vi sarà proposto qualche esercizio
Modellare un problema Modellare un problema con CP Introduzione a ILOG Concert & Solver
Il nostro esempio Supponiamo di dover ottimizzare l’esecuzione di tasks su un sistema multiprocessore Siano: • T = {t0, t1, ..., tn-1} = insieme degli n tasks • P = {p0, p1, ..., pp-1} = insieme dei p processori • dur(ti) = durata del task i-mo • Ogni task esegue su un solo processore • Su ogni processore i task eseguono in sequenza • Il tempo totale di esecuzione non deve superare una deadline Obiettivo: usare il minimo numero di processori Modellare un problema
t0 t1 t2 t3 t4 t0 t2 t3 p0 t1 t4 p1 p2 Semplice, no? • Come lo affrontiamo? • Vediamo che strumenti abbiamo a disposizione Modellare un problema
Introduzione ad ILOG CP Che cosa è ILOG? • ILOG è una azienda francese • Produce strumenti per la gestione efficiente di processi di manageriali e per la soluzione di problemi di ottimizzazione A noi ci interessano questi In particolare: • Strumenti per modellare problemi • Strumenti per risolvere problemi Modellare un problema
Linguaggio di Modellazione MILP Linguaggio di modellazione a vincoli AMPL Cplex Math Programming Solver Semi-automatic CP solver OPL CP Optimizer CP Solvers Solver Scheduler Dispatcher Concert ILOG CP API per modellazione Modellare un problema
In pratica... • AMPL e OPL sono linguaggi per descrivere modelli • Concert è una libreria in C++ • Fornisce classi e funzioni per costruire modelli Per esempio: • class IloModel modello • class IloIntVar variabile intera • class IloNumVar variabile reale • class IloBoolVar variabile logica • class IloConstraintvincolo • ... • Solver è un risolutore (sempre in C++) che dato un modello (ex. costruito in concert) trova una soluzione Modellare un problema
Allora da dove partiamo? Da carta e penna Modellare un problema
Un primo modello One dimensional bin packing: • Variabili: • Vincoli: • Ogni task esegue su un solo processore • Il tempo totale di esecuzione non deve superare una deadline Modellare un problema
Nel complesso: obiettivo: soggetto a: Formulato il modello su carta, possiamo “costruirlo” usando Concert Modellare un problema
Struttura di un programma ILOG #include <ilconcert/ilomodel.h> #include <ilsolver/ilosolver.h> ILOSTLBEGIN int main(int argc, char** argv){ IloEnv env; IloModel model(env); <inizializzazione modello> <invocazione del solver> <output> } Per usare concert e solver MACRO: definisce il namespace std IloEnv: gestore della memoria, fa da contenitore per il modello Classe che rappresenta un modello Variabili e vincoli vengono “aggiunti” al modello Modellare un problema
Costruzione del modello • Per inizializzare un modello innanzitutto vanno definite le sue variabili: IloIntVarArray Y(env, nproc, 0, 1); IloArray<IloIntVarArray> X(env, nproc); for(int i = 0; i < nproc; i++){ X[i] = IloIntVarArray(env, ntask, 0, 1); } Array di arrays • Parametri di IloIntVarArray: • IloEnv: gestore della memoria • IloInt: dimensione • IloInt: lower bound del dominio • IloInt: upper bound del dominio Array di variabili intere Modellare un problema
Costruzione del modello • Poi vanno specificati i vincoli Costruisce una espressione vuota... ...che può essere estesa con i normali operatori aritmetici! for(int j = 0; j < ntask; j++){ IloIntExpr sum(env); for(int i = 0; i < nproc; i++){ sum += X[i][j]; } model.add(sum == 1); } I vincoli vanno aggiunti al modello (vengono aggiunte anche le variabili su cui sono definiti) L’operatore di confronto (“==“) tra espressioni restituisce un vincolo Modellare un problema
Costruzione del modello for(int i = 0; i < nproc; i++){ IloIntExpr sum(env); for(int j = 0; j < ntask; j++){ sum += durations[j]*X[i][j]; } model.add(sum <= deadline*Y[i]); } Modellare un problema
Costruzione del modello • In qualunque punto (dopo la definizione delle variabili) si può specificare una funzione obiettivo model.add(IloMinimize(env, IloSum(Y))); Un modo compatto per costruire una espressione di somma (Y è un array) Indica che la soluzione deve minimizzare l’espressione specificata Modellare un problema
Utilizzo del solver Costruisce un solver (classe IloSolver) <inizializzazione del modello> IloSolver solver(env); solver.extract(model); solver.solve(); <output> Trova la soluzione ottima (se esiste) “estrae” il modello • Prima di iniziare la ricerca di una soluzione il modello deve essere convertito nel formato interno del solver • Questa operazione si chiama “estrazione” • Classi concert: Ilo... (ex IloIntVar) • Classi solver: Ilc... (ex. IlcIntVar) Modellare un problema
Input & Output intntask = 10; intnproc = 6; int durations[] = {3, 4, 7, 2, 2, 8, 7, 10, 8, 9}; int deadline = 16; Number of fails : 9282 Number of choice points : 9312 ... Running time since creation : 0.2 Y[0]:0 Y[1]:0 Y[2]:1 Y[3]:1 Y[4]:1 Y[5]:1 Modellare un problema
Stili di modellazione Modelli alternativi
Un altro modello • CP ha un linguaggio di modellazione molto “ricco” (molti tipi di vincoli) • Questo permette di migliorare le performance adottando stili di modellazione differenti Esempio: Cambiamo le variabili! Solo una variabile per task IloIntVarArray Proc(env, ntask, 0, nproc-1); • Tra l’altro in questo modo ogni task esegue per forza su un solo processore Stili di modellazione
Un altro modello Come definire i vincoli di deadline? METAVINCOLI • Un vincolo può essere utilizzato all’interno di una espressione: denota 1 se è vero, 0 se è falso for(int i = 0; i < ntask; i++){ IloIntExpr sum(env); for(int j = 0; j < ntask; j++){ sum += durations[j]*(Proc[j] == i); } model.add(sum <= deadline); } Stili di modellazione
Un altro modello Cambia anche la funzione obiettivo: Il più alto indice di processore utilizzato model.add(IloMinimize(env, IloMax(Proc))); Il nuovo modello ha la stessa semantica (=> le stesse soluzioni), ma un numero minore di variabili e una diversa funzione obiettivo Stili di modellazione
Output Number of fails : 425 Number of choice points : 432 ... Running time since creation : 0.03 Proc[0]:0 Proc[1]:0 Proc[2]:0 Proc[3]:0 Proc[4]:1 Proc[5]:2 Proc[6]:3 Proc[7]:1 Proc[8]:2 Proc[9]:3 Stili di modellazione
Esercizio 1 (base2.cpp) • Cosa succede con i due modelli aumentando il numero di processori a 16? • Perché? Stili di modellazione
Vincoli globali Utilizzo dei vincoli globali
Perché i vincoli globali I vincoli globali modellano alcune sottostrutture particolarmente frequenti. Hanno diversi vantaggi: • modellazione più compatta • propagazione più efficace • (a volte) propagazione più efficiente Esempio: Gli ultimi ptask devono essere assegnati a processori diversi Vincoli globali
Modello con vincoli binari • Modellando con vincoli “!=“: for (inti = ntask-nproc; i < ntask; i++){ for (int j = i+1; j < ntask; j++){ model.add(Proc[i] != Proc[j]); } } • Output: Number of fails : 44496 Number of choice points : 44505 ... Running time since creation : 1.5 Vincoli globali
Modello con alldifferent • Alldifferent in ILOG IloAllDiff(IloEnv, IloIntVarArray) IloAllDiffCt IloDistributeCt IloSequenceCt IloAllMinDistanceCt IloPartitionCt IloAllNullIntersectCt IloEqUnionCt IloNotOverlapCt IloBoxCt IlcFilterLevelConstraint • Nel nostro caso: IloIntVarArray diff(env); for(int j = ntask-nproc; j < ntask; j++) diff.add(Proc[j]); model.add(IloAllDiff(env, diff)); IloExtendedLevel IloMediumLevel IloBasicLevel IloLowLevel IlcFilterLevel • ATTENZIONE! Dopo aver estratto il modello: solver.setDefaultFilterLevel(IloAllDiffCt, IloExtendedLevel); Vincoli globali
Output Number of fails : 8 Number of choice points : 16 ... Running time since creation : 0 problem solved Proc[0]:0 Proc[1]:0 Proc[2]:0 Proc[3]:1 Proc[4]:0 Proc[5]:1 Proc[6]:2 Proc[7]:3 Proc[8]:4 Proc[9]:5 IMPORTANTE: ILOG Solver permette anche di definire nuovi vincoli, ma per il momento non ci interessa... Vincoli globali
Esercizio 2 (base2.cpp) • Nuovo vincolo: non più di tre task per processore • SUGGERIMENTO: il vincolo gcc in ILOG è: IloDistribute(IloEnv, IloIntVarArray cards,IloIntArrayvals, IloIntVarArrayvars) • Ricordate che dopo l’estrazione va modificato l’algoritmo di filtering... solver.setDefaultFilterLevel(???, ???); Vincoli globali
Strategie di ricerca Modificare la strategia di ricerca
Search strategy CP permette l’impiego di diverse strategie di ricerca • Per esempio si può scegliere il criterio con cui scegliere la variabile su cui fare branching IloGenerate(IloEnv, IloIntVarArray, IloChooseIntIndex) IloChooseFirstUnboundIntIloChooseMaxMaxIntIloChooseMaxMinIntIloChooseMaxRegretMaxIloChooseMaxRegretMinIloChooseMaxSizeIntIloChooseMinMaxIntIloChooseMinMinIntIloChooseMinRegretMaxIloChooseMinRegretMinIloChooseMinSizeInt E’ un IloGoal, va passato come argomento di solver.solve(...) First Fail Principle Strategie di ricerca
Output solver.solve(IloGenerate(env, Proc, IloChooseMinSizeInt)) Number of fails : 71 Number of choice points : 77 ... Running time since creation : 0.01 IMPORTANTE: ILOG Solver permette anche di definire nuove strategie o di intervenire sulla ricerca in modo ancora più complesso, ma per il momento non ci interessa... Search stratgies
Esercizio 3 (base.cpp, base2.cpp) • Cosa succede con altre strategie di ricerca? • Cosa succede utilizzando il first fail principle (IloChooseMinSizeInt) nel primo modello? Perché? Search stratgies
Simmetrie Eliminazione delle simmetrie
Simmetrie I processori sono risorse simmetriche; per esempio le due allocazioni: Proc[0]:0 Proc[1]:0 Proc[2]:0 Proc[3]:0 Proc[4]:1 Proc[5]:2 Proc[6]:3 Proc[7]:1 Proc[8]:2 Proc[9]:3 Proc[0]:1 Proc[1]:1 Proc[2]:1 Proc[3]:1 Proc[4]:0 Proc[5]:2 Proc[6]:3 Proc[7]:0 Proc[8]:2 Proc[9]:3 e Sono del tutto equivalenti! Simmetrie
Simmetrie Una soluzione è quella di forzare un ordine di preferenza tra i processori: se p0 è libero non utilizzare uno dei processori seguenti • Si possono aggiungere dei vincoli per vietare le allocazioni che non rispetterebbero il criterio • Per ogni processore teniamo traccia del task di indice più basso allocato su di esso (dopo vedremo come) IloIntVarArrayminItem(env, nproc, 0, ntask-1); • Il task di indice più basso su p0 deve essere minore di quello su p1, e così via for(inti = 0; i < nproc-1; i++){ model.add(minItem[i] < minItem[i+1]); } Simmetrie
Simmetrie Come ottenere minItem? model.add(minItem(Proc[0]) == 0); for(inti = 1; i < ntask; i++){ IloConstraintxpr = Proc[i] != Proc[i-1]; for(int j = i-2; j >= 0; j--){ xpr = xpr && (Proc[i] != Proc[j]); } model.add(IloIfThen(env, xpr, minItem(Proc[i]) == i)); } Element constraint in ILOG METAVINCOLI: i vincoli possono essere combinati in espressioni logiche! Simmetrie
Output Number of fails : 84 Number of choice points : 92 ... Running time since creation : 0.01 Proc[0]:0 Proc[1]:0 Proc[2]:0 Proc[3]:0 Proc[4]:1 Proc[5]:2 Proc[6]:3 Proc[7]:1 Proc[8]:2 Proc[9]:3 Simmetrie
Scheduling con ILOG Introduzione ad ILOG Scheduler
ILOG Scheduler • Un’altra libreria in C++ • Fornisce strumenti per modellare e risolvere problemi di scheduling • Si appoggia al Solver per la soluzione del problema • La classe IlcScheduler permette di accedere alla soluzione Alcuni concetti chiave: • Attività • Vincoli temporali • Risorse • ... Scheduling con ILOG
Attività Definite con: IloActivity(env, IloInt duration); IloActivity(env, IloNumVar duration); Una attività introduce tre variabili: START, END, DUR • END = START + DUR IloArray<IloActivity> acts(env, ntask); for(inti = 0; i < ntask; i++){ acts[i] = IloActivity(env, durations[i]); } Scheduling con ILOG
Vincoli temporali IloActivity::startsAfterEnd(IloActivity); IloActivity::startsAfterStart(IloActivity); ... ...Restituiscono vincoli che forzano relazioni di precedenza IloActivity::startsBefore(...); IloActivity::startsAfter(...); ... ...Restituiscono vincoli temporali rispetto ad istanti fissati (o a variabili intere) for(inti = 0; i < nprec; i++){ model.add(acts[precTo[i]].startsAfterEnd(acts[precFrom[i]])); } Scheduling con ILOG
Risorse Per ora consideriamo solo risorse unarie: IloUnaryResource(IloEnv) • Il metodo IloActivity::requires(...) restituisce un vincolo che va aggiunto al modello IloArray<IloUnaryResource> pres(env, nproc); for(inti = 0; i < nproc; i++) pres[i] = IloUnaryResource(env); for(inti = 0; i < nproc; i++){ for(int j = 0; j < ntask; j++){ model.add(IloIfThen(env, Proc[j] == i, acts[j].requires(pres[i]))); } } Scheduling con ILOG
Input & Output intnprec = 5; intprecFrom[] = {0, 0, 4, 6, 3}; intprecTo[] = {1, 2, 5, 5, 8}; Goal: IloGenerate(env, Proc, ...) && IloSetTimesForward(env) Proc[0]:0 task[0]: [0 -- 3 --> 3] Proc[1]:0 task[1]: [3 -- 4 --> 7] Proc[2]:1 task[2]: [9 -- 7 --> 16] Proc[3]:2 task[3]: [2 -- 2 --> 4] Proc[4]:2 task[4]: [0 -- 2 --> 2] Proc[5]:0 task[5]: [7 -- 8 --> 15] Proc[6]:3 task[6]: [0 -- 7 --> 7] Proc[7]:2 task[7]: [4 -- 10 --> 14] Proc[8]:3 task[8]: [7 -- 8 --> 15] Proc[9]:1 task[9]: [0 -- 9 --> 9] Scheduling con ILOG
Esercizio 4 (sched.cpp) • Cosa succede modificando il livello di propagazione? IloSchedulerEnvschedEnv(env); schedEnv.getResourceParam().setCapacityEnforcement(...); Notate cheIloExtendedabilita edge finder • Nuovo vincolo: i task 0, 3, 6, 9 devono accedere ad una memoria a due vie Risorsa a capacitàmaggioredi 1: IloDiscreteResource Scheduling con ILOG
Minimizzare il tempo di esecuzione Introduciamounanuovavariabile (makespan): IloIntVarmakespan(env); Tutte le attivitàdevonoterminare prima del tempo diesecuzione: for(inti = 0; i < ntask; i++) model.add(acts[i].endsBefore(makespan)); Nuovoobiettivo: model.add(IloMinimize(env, makespan)); Goal ottimizzato: IloGenerate(env, Proc, ...) && IloSetTimesForward(env, makespan) Scheduling con ILOG
Output Proc[0]:0 task[0]: [0 -- 3 --> 3] Proc[1]:0 task[1]: [10 -- 4 --> 14] Proc[2]:0 task[2]: [3 -- 7 --> 10] Proc[3]:1 task[3]: [2 -- 2 --> 4] Proc[4]:1 task[4]: [0 -- 2 --> 2] Proc[5]:1 task[5]: [7 -- 8 --> 15] Proc[6]:2 task[6]: [0 -- 7 --> 7] Proc[7]:3 task[7]: [0 -- 10 --> 10] Proc[8]:2 task[8]: [7 -- 8 --> 15] Proc[9]:4 task[9]: [0 -- 9 --> 9] Scheduling con ILOG
Esercizio 5 (schedMK.cpp) • Cosa succede usando il goal IloSetTimesFOrward non ottimizzato? • Cosa succede eliminando le relazioni di precedenza? Scheduling con ILOG
Basta teoria ;-) Adesso mettiamo le mani in pasta