Calcolo della struttura di sparsità per matrici di elementi finiti


13

Domanda: quali metodi sono disponibili per calcolare in modo accurato ed efficiente la struttura di sparsità di una matrice di elementi finiti?

Info: sto lavorando su un risolutore di equazioni di pressione di Poisson, usando il metodo di Galerkin con una base quadratica di Lagrange, scritto in C, e usando PETSc per l'archiviazione di matrici sparse e routine KSP. Per usare PETSc in modo efficiente, devo pre-allocare memoria per la matrice di rigidità globale.

Attualmente, sto facendo un gruppo finto per stimare il numero di nonzeros per riga come segue (pseudocodice)

int nnz[global_dim]
for E=1 to NUM_ELTS
  for i=1 to 6
    gi = global index of i 
    if node gi is free
      for j=1 to 6
        gj = global index of j
        if node gj is free 
          nnz[i]++

Questo, tuttavia sopravvaluta nnz perché alcune interazioni nodo-nodo possono verificarsi in più elementi.

Ho considerato di provare a tenere traccia delle interazioni che ho trovato, ma non sono sicuro su come farlo senza usare molta memoria. Potrei anche passare in rassegna i nodi e trovare il supporto della funzione base centrata su quel nodo, ma poi dovrei cercare tutti gli elementi per ciascun nodo, il che sembra inefficiente.

Ho trovato questa recente domanda, che conteneva alcune informazioni utili, soprattutto da Stefano M, che ha scritto

il mio consiglio è di implementarlo in Python o C, applicando alcuni concetti teorici del grafico, cioè considerare gli elementi della matrice come spigoli in un grafico e calcolare la struttura di sparsità della matrice di adiacenza. Elenco di elenchi o dizionario di chiavi sono scelte comuni.

Sto cercando maggiori dettagli e risorse su questo. Devo ammettere che non conosco molta teoria dei grafi e non ho familiarità con tutti i trucchi CS che potrebbero essere utili (mi sto avvicinando dal punto di vista matematico).

Grazie!

Risposte:


5

La tua idea di tenere traccia di quali io, j interazioni hai trovato può funzionare, penso che sia il "trucco CS" a cui tu e Stefano M vi riferite. Ciò equivale a costruire la tua matrice sparsa in formato elenco di elenchi .

Non sono sicuro di quanto CS hai, quindi mi scuso se questo ti è già noto: in una struttura di dati di elenchi collegati , ogni voce memorizza un puntatore alla voce successiva e alla voce precedente. È economico aggiungere ed eliminare voci da, ma non è così semplice trovare elementi al loro interno - potresti doverli sfogliare tutti.

Quindi, per ciascun nodo i, si memorizza un elenco collegato. Quindi ripeti tutti gli elementi; se trovi due nodi i e j collegati, vai nella lista dei collegamenti i. Se j non è già lì, lo aggiungi all'elenco e allo stesso modo aggiungi i all'elenco j. È più semplice se li aggiungi in ordine.

Dopo aver popolato il tuo elenco di elenchi, ora conosci il numero di voci diverse da zero in ciascuna riga della matrice: è la lunghezza dell'elenco di quel nodo. Queste informazioni sono esattamente ciò di cui hai bisogno per preallocare una matrice sparsa nella struttura dei dati della matrice PETSc. Quindi puoi liberare la tua lista di liste perché non ne hai più bisogno.

Tuttavia, questo approccio presuppone che tutto ciò che hai sia l'elenco di quali nodi contiene ciascun elemento.

Alcuni pacchetti di generazione di mesh, ad esempio Triangle , possono generare non solo un elenco di elementi e quali nodi contengono, ma anche un elenco di tutti i bordi della triangolazione. In tal caso, non si corre il rischio di sopravvalutare il numero di voci diverse da zero: per gli elementi lineari a tratti, ciascun bordo fornisce esattamente 2 voci della matrice di rigidezza. Stai usando un quadratico a tratti, quindi ogni fronte conta per 4 voci, ma ottieni l'idea. In tal caso, è possibile trovare il numero di voci diverse da zero per riga con un passaggio nell'elenco dei bordi utilizzando un array ordinario.

Con questo approccio, devi leggere un file extra dal disco rigido, che potrebbe effettivamente essere più lento rispetto all'utilizzo dell'elenco di elementi se il tuo calcolo effettivo non è così grande. Tuttavia, penso che sia più semplice.


Grazie. Ho una lista dei bordi disponibile, quindi per il momento userò probabilmente il tuo secondo metodo, ma potrei tornare indietro e provare il primo metodo, solo per sporcarmi le mani con elenchi collegati e simili (grazie per l'introduzione ... I ' ho preso solo una classe CS di base, e mentre mi diletto per la programmazione, non so quanto dovrei sulle strutture dati e sugli algoritmi)
John Edwardson

Felice di aiutare! Ho raccolto molte delle mie conoscenze CS da questo: books.google.com/books?isbn=0262032937 - per amore di Dio, leggi sull'analisi ammortizzata. Vale la pena programmare la propria lista di collegamenti o la struttura dei dati dell'albero di ricerca binaria in C.
Daniel Shapero


2

upvoted

Personalmente non conosco alcun modo economico per farlo, quindi semplicemente sopravvaluto il numero, cioè uso un valore ragionevolmente grande per tutte le righe.

Ad esempio, per una mesh perfettamente strutturata composta da elementi esagonali lineari a 8 nodi, i nnz per riga sia nei blocchi diagonali che in quelli diagonali sono dof * 27. Per le maglie esadecimali generate automaticamente e non strutturate, il numero raramente supera dof * 54. Per le scommesse lineari non ho mai avuto la necessità di andare oltre dof * 30. Per alcune maglie con elementi di forma / cattiva forma molto male potrebbe essere necessario utilizzare valori leggermente più grandi.

La penalità è che il consumo di memoria locale (in classifica) è compreso tra 2 e 5 volte, quindi potrebbe essere necessario utilizzare più nodi di calcolo sul cluster rispetto al solito.

A proposito, ho provato a usare elenchi ricercabili, ma il tempo impiegato per determinare la struttura della sparsità era più che l'assemblaggio / risoluzione. Ma la mia implementazione è stata molto semplice e non ho usato le informazioni sui bordi.

L'altra opzione è utilizzare routine come DMMeshCreateExodus come mostrato in questo esempio.


0

Stai cercando di enumerare tutte le connessioni univoche (gi, gj), il che suggerisce di metterle tutte in un contenitore associativo (non duplicante) e quindi contare la sua cardinalità - in C ++ questo sarebbe uno std :: set <std :: pair <int, int>>. Nel tuo pseudocodice, sostituiresti "nnz [i] ++" con "s.insert [pair (gi, gj)]", e quindi il numero finale di nonzeros è s.size (). Dovrebbe essere eseguito nel tempo O (n-log-n), dove n è il numero di nonzeros.

Poiché probabilmente conosci già la gamma di possibili gi, puoi "allargare" la tabella in base all'indice gi per migliorare le prestazioni. Questo sostituisce il tuo set con uno std :: vector <std :: set <int>>. Lo popoli con "v [gi] .insert (gj)", quindi il numero totale di nonzer proviene dalla somma di v [gi] .size () per tutti i gi. Questo dovrebbe funzionare nel tempo O (n-log-k), dove k è il numero di incognite per elemento (sei per te - essenzialmente una costante per la maggior parte dei codici pde, a meno che tu non stia parlando di metodi HP).

(Nota: volevo che questo fosse un commento sulla risposta selezionata, ma era troppo lungo - scusa!)


0

ET×

EiojT={1iof dof jelement io0elSewhere
UN=EETETETE
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.