Progetto MECOM, Programma Operativo Plurifondo 94/99 Misura 4.4 ”Ricerca scientifica e tecnologica” Sviluppi ed applicazioni della meccanica computazionale nella progettazione strutturale in campo civile ed industriale . Una implementazione in C++ di un algoritmo di rinumerazione per la riduzione dell’ampiezza di banda di matrici sparse Antonio Bilotta Report n. 193 Dicembre 1998 Dipartimento di Strutture Università della Calabria 87030 Rende, Cosenza, Italy tel.: +39 0984 494032 - fax: +39 0984 494045 e.mail: [email protected] 1 Una implementazione in C++ di un algoritmo di rinumerazione per la riduzione dell’ampiezza di banda di matrici sparse A. Bilotta Dipartimento Strutture Università della Calabria, 87030 Rende (CS) – Italy e.mail abilotta @ labmec.unical.it Introduzione Molti problemi ingegneristici richiedono la soluzione di un sistema lineare di equazioni del tipo Ax = b, dove x è il vettore che raccoglie i parametri incogniti, b è il vettore dei termini noti, A è una matrice N xN simmetrica definita positiva e, tipicamente, con una struttura a banda. I due metodi più largamente adottati nei programmi ad elementi finiti per la soluzione di tale sistema sono gli schemi profile o skyline e frontale. Lo schema profile si basa, nelle implementazioni solitamente adottate, su un assemblaggio completo della matrice di rigidezza e quindi la sua efficacia computazionale dipende da due aspetti: (i) ottimizzazione delle risorse richieste per la memorizzazione della matrice; (ii) riduzione dei tempi di calcolo necessari alla soluzione del sistema. Lo schema frontale invece scorre le righe del sistema lineare utilizzando al volo solo le equazioni effettivamente attive durante ogni singolo passo del processo di decomposizione della matrice ed eliminazione delle incognite, perciò la sua efficienza dipende essenzialmente dai tempi di calcolo richesti dalla soluzione del sistema. 2 – Algoritmo di rinumerazione: implementazione in C++ – 3 Per quel che riguarda il primo aspetto, ovvero la memoria accupata dalla matrice di rigidezza, una sua ottimizzazione dipende dall’ordinamento delle equazioni il quale, nel metodo ad elementi finiti, corrisponde alla numerazione adottata per i nodi del modello discreto. Di conseguenza tale numerazione, vista la struttura a banda della matrice di rigidezza, deve essere effettuata in modo da minimizzare la somma delle ampiezze di banda delle singole righe. In maniera più formale è possibile definire la quantità P denonimata profile in base alla seguente formula: P = N bi (1) i=1 bi rappresenta l’ampiezza di banda della singola riga del sistema ovvero la differenza di indici dell’ultimo coefficiente non nullo Aij , j > i, e del termine sulla diagonale principale Aii . Risulta quindi fondamentale minizzare il valore assunto da P . D’altra parte anche i tempi di calcolo richiesti dalla soluzione del sistema, ad esempio mediante una classica elinazione alla Gauss, dipendono dalle ampiezze di banda bi . Infatti introducendo la quantità 12 N 1 2 bi (2) F = N i=1 (root–mean–square wavefront) è possibile mostrare come il numero di addizioni e moltiplicazioni richieste per il processo di decomposizione della matrice sia di ordine O(N F 2 ) [1]. Quindi il problema di una soluzione efficiente, rispetto alle risorse di memoria richieste e ai tempi di calcolo impiegati, del sistema lineare Ax = b può essere ridotto al problema della minimizzazione di P . Tale obbiettivo, per un modello discreto ad elementi finiti, è raggiungibile mediante una numerazione adeguata dei nodi che definiscono la struttura analizzata. Nel seguito si intende descrivere l’implementazione in C++, noto linguaggio di programmazione orientato agli oggetti, di un algoritmo di rinumerazione dei nodi proposto da S. W. Sloan in [1]. Vengono quindi ripercorse rapidamente le caratteristiche salienti dell’algoritmo e, mediante dei risultati numerici, si evidenzia l’effettiva convenienza di tale algoritmo rispetto al più noto procedimento di rinumerazione di Cuthill e McKee [2]. Infine si riporta il listato del codice implementato il quale può essere facilmente incluso in un tipico programma per l’analisi ad elementi finiti. Notazione Il problema della numerazione dei nodi di una discretizzazione FEM è equivalente a quello della numerazione di un grafo non orientato. Conviene quindi rifersi alla – Algoritmo di rinumerazione: implementazione in C++ – 4 teoria dei grafi ed introdurre un minimo di terminologia utile alla definizione del problema. Un grafo è formato da un insieme di membri detti nodi e da un insieme di coppie di nodi distinti detti lati. Quando lungo i lati non viene stabilito un particolare senso di percorrenza allora il grafo si dice non orientato. Si noti che loops, ossia nodi connessi con se stessi, e lati multipli, ossia coppie di nodi connesse da più lati, non sono previsti per le tipologie di grafi prese in esame. Per grado di un nodo si intende il numero dei lati che convergono su quest’ultimo e può assumere un valore intero compreso tra 1 e N . Due nodi si dicono connessi se è possibile stabilire una sequenza di lati, percorso, che li congiunge. Se ciò è possibile per qualsiasi coppia di nodi allora il grafo si dice connesso. La distanza tra una coppia di nodi è definita dal numero di lati appartenenti al percorso più breve che li congiunge, mentre il diametro di un grafo è la massima delle distanze tra ogni coppia di nodi. Tutte le possibili coppie di nodi che definiscono il diametro di un grafo sono denominati nodi periferici. Risulta utile introdurre, per il tipo di algoritmo che verrà descritto, lo pseudo-diametro, ovvero una stima del diametro del grafo, e, di conseguenza, i nodi pseudo-periferici, ovvero nodi che definiscono uno pseudo-diametro. Infine un concetto che risulta essere centrale per molti algoritmi di ordinamento: la struttura a livelli a partire da un nodo assegnato o radice. Si tratta di un partizionamento dei nodi che assegna ciascun nodo ad uno dei livelli l1 , l2 , . . . , lh(r) in base alla propria distanza dalla radice r. Tutti i nodi che si trovano alla stessa distanza dalla radice appartengono allo stesso livello. La profondità di una struttura a livelli, h(r), è data semplicemente dal numero di livelli che la compongono, mentre la larghezza è fornita dal numero massimo di nodi che appartengono ad un singolo livello. In modo più formale tale partizionamento può essere rappresentato come L(r) = {l1 , l2 , . . . , lh(r) } dove • l1 = r. • Per i > 1 li è costituito dall’insieme dei nodi che si trovano ad una distanza pari a i − 1 dalla radice. L’algoritmo di Sloan La strategia dell’algoritmo di rinumerazione di Sloan si basa su due passi ben distinti: (i) selezione di due nodi periferici e della struttura a livelli ad essi associata la quale, avendo come radice uno dei due nodi periferici, sarà adeguatamente stretta – Algoritmo di rinumerazione: implementazione in C++ – 5 ed allungata; (ii) rinumerazione dei nodi utilizzando la struttura a livelli ottenuta nel passo precedente. Selezione dei nodi periferici La numerazione vera e propria dei nodi viene preceduta da una fase in cui si effettua una stima del diametro del grafo e quindi si procede alla selezione di due nodi pseudo-periferici da utilizzare successivamente. Tale compito può essere portato a termini nel modo seguente: 1 Ricerca, fra tutti i nodi del grafo, del nodo s avente grado minore. 2 Assumendo come radice s generazione della struttura a livelli L(s). 3 Ordinamento per grado ascendente dei nodi contenuti nel’ultimo livello lh(s) . 4 A partire da lh(s) , generazione di una lista di nodi q contenente per ciascun grado solo un nodo. 5 Inizializzazione w(e) = ∞. 6 Per ogni nodo i ∈ q costruzione della struttura L(i) = {l1 , l2 , . . . , lh(i) }. Se h(i) > h(s) e w(i) < w(e) allora s = i e ritorno al passo 3. Altrimenti se w(i) < w(e) si pone e = i e w(e) = w(i). 7 Uscita con la determinazione dei nodi iniziale s e finale e. Numerazione dei nodi Dopo avere determinato una coppia di nodi pseudo-periferici si può procedere alla rinumerazione dei nodi. La tecnica di rinumerazione fa uso delle seguenti definizioni: un nodo che è stato rinumerato è detto postattivo; nodi adiacenti a nodi postattivi sono invece attivi; qualsiasi nodo adiacente a nodi nodi attivi ma che non è postattivo e neanche attivo viene definito preattivo; tutti gli altri nodi sono inattivi. Premesso ciò l’algoritmo si articola nei seguenti passi: 1 Partenza dell’algoritmo con i nodi terminali s e e. 2 Costruzione della struttura a livelli prendendo come radice il nodo finale e e quindi calcolo per ogni nodo i della distanza, δi , da e. 3 Per tutti i nodi viene assegnato lo stato inattivo e viene calcolata la priorità iniziale pi mediante la formula: pi = W1 δi − W 2(di + 1) W1 e W2 sono dei valori interi e rappresentano dei pesi, di è il grado del nodo i. – Algoritmo di rinumerazione: implementazione in C++ – 6 4 Inizializzazione a zero del contatore l dei nodi che sono stati rinumerati e della coda di priorità q. Assegnazione al nodo s dello stato di preattivo e suo inserimento nella coda ponendo n = 1 e qn = s. 5 Fintantoché la coda di priorità è piena, n > 0, ripetizione dei passi dal 6 al 9. 6 Ricerca nella coda q del nodo avente la massima priorità: pi = maxj∈q {pj }. Sia m l’indice del nodo i tale che qm = i. 7 Eliminazione del nodo i dall coda q ponendo qm = qn e decremento del contatore n = n − 1. Se il nodo non è preattivo si salta al passo successivo. Altrimenti si esaminano tutti in nodi j adiacenti ad i incrementandone la priorità in base alla formula pj = pj + W2 . Inoltre se il nodo j è inattivo viene inserito nella coda di priorità, n = n+1 e qn = j, ponendolo in stato preattivo. 8 Rinumerazione del nodo i. Ciò comporta l’incremento del contatore l, l = l+1, e l’assegnazione ηi = l, dove η contiene le nuove etichette per i nodi. Passaggio allo stato postattivo per il nodo i. 9 Esame di tutti i nodi j adiacenti al nodo appena rinumerato i. Se il generico nodo j non è preattivo non si effettua alcuna operazione. Altrimenti si assegna al nodo j lo stato di nodo attivo, si pone pj = pj + W2 , e si esamina ogni nodo k adiacente a j. Se il nodo k non è postattivo se ne incrementa la priorità pk = pk + W2 . Se il nodo k è inattivo viene inserito nella coda di priorità in stato preattivo ponendo n = n + 1 e qn = k. 10 Uscita dall’algoritmo con la lista η deli nuovi numeri per i nodi del grafo. Una precisazione finale riguarda i pesi W1 e W2 che riflettono l’importanza della distanza dal nodo finale rispetto al grado corrente del nodo. Al fine di ottenere i migliori risultati in [1] vengono suggeriti i seguenti valori: W1 = 1 e W2 = 2. Implementazione L’implementazione dell’algoritmo esaminato è stata effettuata mediante il noto linguaggio di programmazione orientato agli oggetti C++ [3, 4]. L’algoritmo di rinumerazione è stato quindi implementato realizzando una serie di classi dotate delle strutture dati e dei metodi ritenuti più idonei per la risoluzione del problema in esame. Verranno descritte quindi le classi adottate nell’implementazione a partire dalla classe fondamentale Graph che formalizza il concetto di grafo non orientato. – Algoritmo di rinumerazione: implementazione in C++ – 7 La classe Graph incapsula al suo interno tutti i nodi e i lati che definiscono il grafo stesso e con i suoi metodi pubblici consente: l’aggiunta al grafo di nuovi nodi e di nuovi lati; l’applicazione di uno degli algoritmi di rinumerazione proposti rispettivamente da Sloan e da Cuthill e Mckee; il recupero delle nuove numerazioni generate dagli algoritimi. Per la struttura dati fa uso delle classi GraphNode e GraphEdge che raccolgono le informazioni relative a ciascun nodo e a ciascun lato. La classe Graph utilizza la classe RootedLevelStruct che raccoglie le informazioni relative alle strutture a livelli che vengono di volta in volta generate dall’algoritmo. Tale compito viene svolto dal metodo privato Graph::GenerateRLS che, a partire dal nodo radice assegnato, costruisce appunto la struttura a livelli. La classe RootedLevelStruct fa inoltre uso della astrazione intArray, che implementa semplicemente un vettore di interi, per gestire comodamente i vari livelli nella forma di un array di array. In più punti del codice viene utilizzata la classe paramerizzata CArray <type, type> che è una class di libreria che implementa l’array di oggetti generici e che è dotata di una serie di metodi standard che ne consentono un facile uso dinamico. Queste, in sintesi, le caratteristiche principali delle astrazioni adottate. Per quanto riguarda gli altri dettagli la lettura del codice, che utilizza nomi abbastanza autoesplicativi, consente di riconoscere facilmente il compito svolto da ciascuna variabile e di rintracciare i passi dell’algoritmo esposto nella sezione precedente. L’algoritmo di Cuthill e McKee Anche se nel presente lavoro l’interesse è focalizzato sull’algoritmo di Sloan, coviene comunque ricordare brevemente l’organizzazione dell’approccio di rinumerazione dovuto a Cuthill e McKee e del quale nel seguito viene riportata anche un’implementazione in C++. L’algoritmo comincia selezionando una serie di nodi aventi valori ridotti del grado di connessione e che per tale motivi sono dei buoni candidati dai quali può partire la numerazione. Per ciascun nodo selezionato i nodi rimanenti vengono rinumerati seguendo la stuttura a livelli che parte da tale nodo (il nodo di partenza constituisce da solo il primo livello). Quindi all’interno di ciascun livello i nodi vengono rinumerati per grado di connessione crescente. Il processo di rinumerazione di tutta la struttura viene ripetuto per tutti i potenziali nodi di partenza precedentemente selezionati scegliendo, alla fine, la sequenza migliore sulla base della massima ampiezza di banda prodotta. – Algoritmo di rinumerazione: implementazione in C++ – 8 Risultati numerici Al fine di mostrare l’efficacia computazionale dell’algoritimo di rinumerazione proposto da Sloan ma soprattutto per testare l’implementazione in C++ qui proposta, sono stati effettuati alcuni test di confronto sulla base delle geometrie riportate in figura 1. Quest’ultime si riferiscono a dominii bidimensionali discretizzati mediante elementi triangolari aventi su ogni nodo tre gradi di libertà, come avviene per esempio nell’analisi di lastre inflesse (l’abbassamento del nodo e le due rotazioni rispetto agli assi cartesiani contenuti nel piano medio della lastra). Le discretizzazioni sulle quali sono stati applicati i due algoritmi di rinumerazione, per i risultati ottenuti si veda la tabella 1, sono state generate per successive suddivisioni in quattro delle triangolazioni minime necessarie alla definizione della geometria assegnata. Si ottiene in tale modo una mesh più fitta ma caratterizzata da una numerazione molto casuale dei nodi ed assolutamente inadatta alla soluzione del sistema lineare, come è anche dimostrato dai valori iniziali del profile, colonna P0 della tabella 1. Per quanto riguarda i valori assunti da P è utile osservare come essi forniscano direttamente la quantità memoria accupata dalla matrice rigidezza mediante la semplice formula: mem = P ∗ sizeof (double)/1000 [Kb]. Dai risultati ottenuti risulta evidente la superiorità dell’algoritmo di Sloan rispetto al tradizionale algoritmo di Cuthill–McKee in termini soprattutto sia di profile prodotto. Per quanto rigurda i tempi i due approcci mettono in luce una sensibile differenza a favore del Cuthill–McKee ma solo nel caso in cui il numero di nodi caratterizzati da un basso valore del grado di connessione è una piccola porzione del numero totale dei nodi. Infatti per gli ultimi quattro test riportati in tabella si ottengono dei tempi molto vicini in quanto si ha un numero elevato di nodi di contorno. – Algoritmo di rinumerazione: implementazione in C++ – Mesh 9 Nvar P0 PCM PSloan T empoCM [s] T empoSloan [s] S1 75 1761 1014 987 0.002 0.01 S2 243 14220 5382 5193 0.01 0.04 S3 867 144654 33414 32469 0.03 0.16 S4 3247 1732041 231174 226989 0.39 1.26 O1 144 5175 2556 2403 0.002 0.02 O2 480 45024 13947 12849 0.03 0.07 O3 1728 494028 86301 80829 0.10 0.44 O4 6528 6266175 590577 566421 1.51 4.56 H1 135 4716 1971 1764 0.004 0.01 H2 435 38220 11031 9222 0.01 0.07 H3 1534 401022 70263 59526 0.19 0.40 H4 5763 4955343 490263 426246 2.66 4.09 ++1 171 7254 2727 2475 0.004 0.02 ++2 555 60420 15717 13314 0.03 0.09 ++3 1971 644058 101871 88668 0.35 0.60 ++4 7395 8049756 720435 646752 5.09 6.51 Tabella 1: Valori di profile ottenuti prima e dopo l’applicazione degli algoritmi di rinumerazione (I tempi di calcolo riportati sono stati ottenuti compilando il codice in versione release mediante il compilatore Microsoft visual C++ ver. 6.0 ed eseguendo i tests su un computer Pentium Pro a 200 MHz). – Algoritmo di rinumerazione: implementazione in C++ – Figura 1: Geometrie e mesh adottate. 10 – Algoritmo di rinumerazione: implementazione in C++ – Il codice //-----------------------------------------------------------// Graph.h // // Interface for the Graph class and other related classes. // //-----------------------------------------------------------class GraphNode { public: GraphNode(); GraphNode(GraphNode& gn); void operator=(GraphNode& gn); ~GraphNode() {} int degree; int distance; int status; int oldLabel; double priority; }; //-----------------------------------------------------------class GraphEdge { public: GraphEdge() {} GraphEdge(GraphEdge& e); void operator=(GraphEdge& e); ~GraphEdge() {} int i; int j; }; 11 – Algoritmo di rinumerazione: implementazione in C++ – //-----------------------------------------------------------class intArray { public: intArray() intArray(intArray& ia) void operator=(intArray& ia) int& operator[](int i) operator const CArray<int,int>&() int Add(int e) int GetSize() void RemoveAll() void Copy(intArray& ia) ~intArray() {} { array.Copy(ia.array); } { array.Copy(ia.array); } { return array[i]; } { return array; } { return array.Add(e); } { return array.GetSize(); } { array.RemoveAll(); } { array.Copy(ia.array); } { array.RemoveAll(); } private: CArray<int, int> array; }; //-----------------------------------------------------------class RootedLevelStruct { public: RootedLevelStruct(); RootedLevelStruct(int h); ~RootedLevelStruct(); void operator=(RootedLevelStruct& RLS); void RemoveAll(); int height; int width; CArray<intArray, intArray> L; CArray<int, int> q; }; //------------------------------------------------------------ 12 – Algoritmo di rinumerazione: implementazione in C++ – 13 class Graph { public: Graph() {} Graph(int nn, int ne); ~Graph(); BOOL Add(GraphNode& gn); BOOL Add(GraphEdge& e); void SetSize(int nn = 0, int ne = 0) { nodes.SetSize(nn); edges.SetSize(ne); } BOOL ExistEdge(int i, int j); void SloanLabelling(CProgressCtrl* prg); void CutMckLabelling(CProgressCtrl* prg, int initial_band); void GetNewLabels(CArray<int, int>* lbl); private: CArray<GraphNode, GraphNode> nodes; CArray<GraphEdge, GraphEdge> edges; CArray<int, int> labels; int FindSmallestDegree(); BOOL GenerateRLS(int root, RootedLevelStruct* RLS, int xw = 1000000); }; enum {INACTIVE, PREACTIVE, ACTIVE, POSTACTIVE}; //-----------------------------------------------------------// Graph.cpp // // Definition of the declared classes. // //-----------------------------------------------------------#include "Sloan.h" – Algoritmo di rinumerazione: implementazione in C++ – //-----------------------------------------------------------GraphNode::GraphNode() { degree = 0; distance = 0; status = INACTIVE; oldLabel = 0; priority = 0.0; } GraphNode::GraphNode(GraphNode& gn) { degree = gn.degree; distance = gn.distance; status = gn.status; oldLabel = gn.oldLabel; priority = gn.priority; } void GraphNode::operator=(GraphNode& gn) { degree = gn.degree; distance = gn.distance; status = gn.status; oldLabel = gn.oldLabel; priority = gn.priority; } GraphEdge::GraphEdge(GraphEdge& e) { i = e.i; j = e.j; } void GraphEdge::operator=(GraphEdge& e) { i = e.i; 14 – Algoritmo di rinumerazione: implementazione in C++ – j = e.j; } //-----------------------------------------------------------RootedLevelStruct::RootedLevelStruct() { height = width = 0; } RootedLevelStruct::RootedLevelStruct(int h) { width = 0; height = h; L.SetSize(h); } void RootedLevelStruct::operator=(RootedLevelStruct& RLS) { RemoveAll(); width = RLS.width; height = RLS.height; q.Copy(RLS.q); L.SetSize(height); for(int i=0; i<height; i++) L[i].Copy(RLS.L[i]); } void RootedLevelStruct::RemoveAll() { int i; for(i=0; i<height; i++) L[i].RemoveAll(); L.RemoveAll(); q.RemoveAll(); height = width = 0; } RootedLevelStruct::~RootedLevelStruct() { RemoveAll(); 15 – Algoritmo di rinumerazione: implementazione in C++ – } //-----------------------------------------------------------Graph::Graph(int nn, int ne) { nodes.SetSize(nn); edges.SetSize(ne); } Graph::~Graph() { nodes.RemoveAll(); edges.RemoveAll(); labels.RemoveAll(); } BOOL Graph::Add(GraphNode& gn) { return nodes.Add(gn); } BOOL Graph::Add(GraphEdge& e) { nodes[e.i].degree++; nodes[e.j].degree++; return edges.Add(e); } int Graph::FindSmallestDegree() { int nn = nodes.GetSize(); int md = 1000000; if( !nn ) return -1; for(int i=0, mi=0; i<nn; i++) if( nodes[i].degree < md ) { mi = i; md = nodes[i].degree; } return mi; 16 – Algoritmo di rinumerazione: implementazione in C++ – 17 } BOOL Graph::GenerateRLS(int root, RootedLevelStruct* RLS, int xw) { intArray emptylevel, level0, level1; int k, n, e, nl; int added = 1, w1 = 0; int nn = nodes.GetSize(), ne = edges.GetSize(), dst = 0; for(n=0; n<nn; n++) nodes[n].distance = -1; RLS->RemoveAll(); RLS->L.Add(emptylevel); RLS->L[dst].Add(root); RLS->height = 1; RLS->width = 1; nodes[root].distance = dst; level0.Add(root); while( added < n ) { nl = level0.GetSize(); for(k=0; k<nl; k++) for(e=0; e<ne; e++) { if( edges[e].i == level0[k] && nodes[edges[e].j].distance == -1 ) { level1.Add(edges[e].j); nodes[edges[e].j].distance = dst+1; w1++; } else if( edges[e].j == level0[k] && nodes[edges[e].i].distance == -1 ) { level1.Add(edges[e].i); nodes[edges[e].i].distance = dst+1; w1++; } else ; } if( w1 > xw ) return FALSE; if( nl = level1.GetSize() ) { dst++; RLS->height++; RLS->width = max(RLS->width, w1); RLS->L.Add(emptylevel); level0.RemoveAll(); – Algoritmo di rinumerazione: implementazione in C++ – 18 for(k=0; k<nl; k++) { RLS->L[dst].Add(level1[k]); level0.Add(level1[k]); added++; } } level1.RemoveAll(); w1 = 0; } RLS->q.Copy(RLS->L[dst]); BOOL bubbled; nl = RLS->q.GetSize(); do { bubbled = FALSE; for(k=0; k<nl-1; k++) { if( nodes[RLS->q[k]].degree > nodes[RLS->q[k+1]].degree ) { int box = RLS->q[k]; RLS->q[k] = RLS->q[k+1]; RLS->q[k+1] = box; bubbled = TRUE; } else if( nodes[RLS->q[k]].degree == nodes[RLS->q[k+1]].degree ) { RLS->q.RemoveAt(k--); nl--; } else ; } } while( bubbled ); return TRUE; } BOOL Graph::ExistEdge(int i, int j) { BOOL exist = FALSE; int e, ne = edges.GetSize(); for(e=0; e<ne; e++) { if( (edges[e].i == i && edges[e].j == j) || (edges[e].i == j && edges[e].j == i) ) – Algoritmo di rinumerazione: implementazione in C++ – 19 exist = TRUE; } return exist; } void Graph::GetNewLabels(CArray<int, int>* lbl) { lbl->RemoveAll(); lbl->Copy(labels); } //-----------------------------------------------------------// Sloan renumbering algorithm //-----------------------------------------------------------void Graph::SloanLabelling(CProgressCtrl* prg) { int start, end, i; RootedLevelStruct startRLS, endRLS, iRLS; BOOL found_peripheral = FALSE; start = FindSmallestDegree(); GenerateRLS(start, &startRLS); endRLS.width = 1000000; // Selection of pseudo-peripheral nodes while( !found_peripheral ) { prg->SetRange(1, startRLS.q.GetSize()); for(i=0; i<startRLS.q.GetSize(); i++) { prg->SetPos(i+1); if( GenerateRLS(startRLS.q[i], &iRLS, endRLS.width) ) { if( iRLS.height > startRLS.height && iRLS.width < endRLS.width ) { start = startRLS.q[i]; startRLS = iRLS; found_peripheral = FALSE; break; } if( iRLS.width < endRLS.width ) { end = startRLS.q[i]; endRLS = iRLS; – Algoritmo di rinumerazione: implementazione in C++ – found_peripheral = TRUE; } } } } GenerateRLS(end, &endRLS); // Node labelling CArray<int, int> queue; double maxp, pr; double W1 = 1.0, W2 = 2.0; int i_maxp, ni, nj, nk, lbl; int ne = edges.GetSize(), nn = nodes.GetSize(); GraphNode *pn; for(i=0, pn=nodes.GetData(); i<nn; i++, pn++) { pn->priority = W1*pn->distance - W2*(pn->degree+1); pr = pn->priority; pn->status = INACTIVE; } labels.RemoveAll(); labels.SetSize(nn); lbl = 0; nodes[start].status = PREACTIVE; queue.Add(start); prg->SetRange(1, nn); while( queue.GetSize() ) { maxp = -100000; for(i=0; i<queue.GetSize(); i++) { if( maxp < nodes[queue[i]].priority ) { maxp = nodes[queue[i]].priority; i_maxp = i; ni = queue[i]; } else if( maxp == nodes[queue[i]].priority && nodes[queue[i]].degree < nodes[ni].degree ) { i_maxp = i; ni = queue[i]; } 20 – Algoritmo di rinumerazione: implementazione in C++ – else; } queue.RemoveAt(i_maxp); if( nodes[ni].status == PREACTIVE ) { GraphEdge *pe; for(i=0, pe=edges.GetData(); i<ne; i++, pe++) { if( pe->i == ni ) nj = pe->j; else if( pe->j == ni ) nj = pe->i; else nj = -1; if( nj != -1 ) { nodes[nj].priority += W2; if( nodes[nj].status == INACTIVE ) { queue.Add(nj); nodes[nj].status = PREACTIVE; } } } } prg->SetPos(lbl+1); labels[lbl] = ni; lbl++; nodes[ni].status = POSTACTIVE; GraphEdge *pe; for(i=0, pe=edges.GetData(); i<ne; i++, pe++) { if( pe->i == ni ) nj = pe->j; else if( pe->j == ni ) nj = pe->i; else nj = -1; if( nj != -1 && nodes[nj].status == PREACTIVE ) { nodes[nj].status = ACTIVE; nodes[nj].priority += W2; GraphEdge *pek; int k; for(k=0, pek=edges.GetData(); k<ne; k++, pek++) { if( pek->i == nj ) nk = pek->j; else if( pek->j == nj ) nk = pek->i; else nk = -1; if( nk != -1 && nodes[nk].status != POSTACTIVE ) nodes[nk].priority += W2; 21 – Algoritmo di rinumerazione: implementazione in C++ – if( nk != -1 && nodes[nk].status == INACTIVE ) { queue.Add(nk); nodes[nk].status = PREACTIVE; } } } } } } //-----------------------------------------------------------// Cuthill McKee renumbering algorithm //-----------------------------------------------------------void Graph::CutMckLabelling(CProgressCtrl* prg, int initial_band) { int i, j, ni, nj, n, nn, ne, curr_band, band; CArray<int, int> level0, level1; GraphNode *pn; GraphEdge *pe; nn = nodes.GetSize(); ne = edges.GetSize(); labels.RemoveAll(); labels.SetSize(nn); band = initial_band; prg->SetRange(1, nn); int aDegree = 0; for(n=0, pn=nodes.GetData(); n<nn; n++, pn++) aDegree += pn->degree; aDegree /= nn; aDegree--; for(n=0; n<nn; n++) { prg->SetPos(n+1); int lbl = 0; if( nodes[n].degree < aDegree ) 22 – Algoritmo di rinumerazione: implementazione in C++ – 23 for(i=0, pn=nodes.GetData(); i<nn; i++, pn++) pn->status = INACTIVE; curr_band = 0; level0.RemoveAll(); level0.Add(n); level1.RemoveAll(); while( level0.GetSize() ) { int s1, s0 = level0.GetSize(); // rinumerazione del livello corrente e costruzione del successivo for(i=0; i<s0; i++) { ni = level0[i]; nodes[lbl].oldLabel = ni; nodes[ni].status = POSTACTIVE; lbl++; for(j=0, pe=edges.GetData(); j<ne; j++, pe++) { if( pe->i == ni ) nj = pe->j; else if( pe->j == ni ) nj = pe->i; else nj = -1; if( nj != -1 && nodes[nj].status == INACTIVE ) { level1.Add(nj); nodes[nj].status = ACTIVE; } } } // controllo sulla convenienza della rinumerazione curr_band = max(curr_band, s1 = level1.GetSize()); if( curr_band >= band ) break; // ordinamento del livello successivo per grado di connessione crescente BOOL bubbled; do { bubbled = TRUE; int *pl, *pl1; for(i=0, pl=level1.GetData(), pl1=pl+1; i<s1-1; i++, pl++, pl1++) if( nodes[*pl1].degree < nodes[*pl].degree ) { int box = *pl1; *pl1 = *pl; *pl = box; bubbled = FALSE; – Algoritmo di rinumerazione: implementazione in C++ – 24 } } while( !bubbled ); level0.RemoveAll(); level0.Copy(level1); level1.RemoveAll(); } // rinumerazione conveniente if( curr_band < band ) { for(i=0, pn=nodes.GetData(); i<nn; i++, pn++) labels[i] = pn->oldLabel; band = curr_band; } } } //------------------------------------------------------------ Ringraziamenti Il presente lavoro è un contributo al programma di ricerca Progetto MECOM, Programma Operativo Plurifondo 94/99 - Misura 4.4 ”Ricerca scientifica e tecnologica”. Riferimenti bibliografici [1] S. W. Sloan, ‘A FORTRAN program for profile and wavefront reduction’ Int. J. Num. Meth. Eng., 28, 2651–2679 (1989). [2] E. Cuthill and J. McKee, ‘Reducing the bandwith of sparse symmetric matrices’ Proc. ACM 24th Nat. Conf. ACM, New York, 157–172 (1969). [3] B. Stroustrup, Il linguaggio C++, Addison-Wesley, Milano (1990). [4] G. Booch, Object-oriented anlysis and design, Benjamin/Cummings Publishing Company, California (1994).