Cálculo de la estructura de dispersión para matrices de elementos finitos.

13

Pregunta: ¿Qué métodos están disponibles para calcular con precisión y eficiencia la estructura de dispersión de una matriz de elementos finitos?

Información: estoy trabajando en un solucionador de ecuaciones de presión de Poisson, usando el método de Galerkin con una base cuadrática de Lagrange, escrito en C, y usando PETSc para el almacenamiento de matriz dispersa y las rutinas KSP. Para usar PETSc de manera eficiente, necesito preasignar memoria para la matriz de rigidez global.

Actualmente, estoy haciendo un montaje simulado para estimar el número de nonzeros por fila de la siguiente manera (pseudocódigo)

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]++

Sin embargo, esto sobreestima nnz porque algunas interacciones nodo-nodo pueden ocurrir en múltiples elementos.

He considerado intentar hacer un seguimiento de las interacciones i, j que he encontrado, pero no estoy seguro de cómo hacerlo sin utilizar mucha memoria. También podría recorrer los nodos y encontrar el soporte de la función base centrada en ese nodo, pero luego tendría que buscar a través de todos los elementos para cada nodo, lo que parece ineficiente.

Encontré esta pregunta reciente, que contenía información útil, especialmente de Stefano M, quien escribió

mi consejo es implementarlo en python o C, aplicando algunos conceptos teóricos de gráficos, es decir, considerar los elementos en la matriz como bordes en un gráfico y calcular la estructura de dispersión de la matriz de adyacencia. La lista de listas o el diccionario de claves son opciones comunes.

Estoy buscando más detalles y recursos sobre esto. Es cierto que no conozco mucha teoría de gráficos, y no estoy familiarizado con todos los trucos de CS que podrían ser útiles (me estoy acercando a esto desde el lado matemático).

¡Gracias!

John Edwardson
fuente

Respuestas:

5

Su idea de hacer un seguimiento de las interacciones i, j que ha encontrado puede funcionar, creo que ese es el "truco CS" al que usted y Stefano M se refieren. Esto equivale a construir su matriz dispersa en formato de lista de listas .

No estoy seguro de cuánto CS tiene, así que le pido disculpas si ya lo sabe: en una estructura de datos de lista vinculada , cada entrada almacena un puntero a la entrada posterior y a la entrada anterior. Es barato agregar y eliminar entradas, pero no es tan simple encontrar elementos en él; es posible que tenga que revisarlas todas.

Entonces, para cada nodo i, almacena una lista vinculada. Luego iteras a través de todos los elementos; si encuentra que dos nodos i y j están conectados, vaya a buscar en la lista vinculada de i. Si j aún no está allí, lo agrega a la lista y también agrega i a la lista de j. Es más fácil si los agrega en orden.

Una vez que haya completado su lista de listas, ahora sabe el número de entradas distintas de cero en cada fila de la matriz: es la longitud de la lista de ese nodo. Esta información es exactamente lo que necesita para preasignar una matriz dispersa en la estructura de datos de matriz de PETSc. Entonces puede liberar su lista de listas porque ya no la necesita.

Sin embargo, este enfoque supone que todo lo que tiene es la lista de los nodos que contiene cada elemento.

Algunos paquetes de generación de mallas, por ejemplo , Triángulo , pueden generar no solo una lista de elementos y qué nodos contienen, sino también una lista de cada borde en su triangulación. En ese caso, no corre el riesgo de sobreestimar el número de entradas distintas de cero: para elementos lineales por partes, cada borde le proporciona exactamente 2 entradas de matriz de rigidez. Está utilizando una división cuadrática por partes, por lo que cada borde cuenta para 4 entradas, pero se entiende la idea. En ese caso, puede encontrar el número de entradas distintas de cero por fila con una pasada a través de la lista de bordes utilizando una matriz ordinaria.

Con ese enfoque, debe leer un archivo extra grande del disco duro, que en realidad podría ser más lento que usar la lista de elementos si su cálculo real no es tan grande. Sin embargo, creo que es más simple.

Daniel Shapero
fuente
Gracias. Tengo una lista de borde disponible, por lo que probablemente usaré su segundo método por ahora, pero podría volver y probar el primer método, solo para ensuciarme las manos con listas vinculadas y demás (gracias por la introducción ... I ' solo he tomado una clase básica de CS, y aunque sé cómo programar, no sé tanto como debería sobre las estructuras de datos y los algoritmos)
John Edwardson
¡Feliz de ayudar! Aprendí mucho de mi conocimiento de CS de esto: books.google.com/books?isbn=0262032937 - por el amor de Dios, lea sobre análisis amortizado. Vale la pena programar su propia lista vinculada o estructura de datos de árbol de búsqueda binaria en C
Daniel Shapero
5

Si especifica su malla como DMPlex y su diseño de datos como PetscSection, DMCreateMatrix () le dará la matriz correctamente preasignada automáticamente. Estos son ejemplos de PETSc para el problema de Poisson y el problema de Stokes .

Matt Knepley
fuente
2

Votado

Personalmente no conozco ninguna forma barata de hacer esto, así que simplemente sobreestimo el número, es decir, uso un valor razonablemente grande para todas las filas.

Por ejemplo, para una malla perfectamente estructurada hecha de elementos hexadecimales lineales de 8 nodos, los nnzs por fila en los bloques diagonales y fuera de la diagonal son dof * 27. Para la mayoría de las mallas hexagonales generadas automáticamente y completamente desestructuradas, el número rara vez excede dof * 54. Para las pruebas lineales nunca he tenido la necesidad de ir más allá de dof * 30. Para algunas mallas con elementos de muy baja forma / relación de aspecto baja, es posible que deba usar valores ligeramente mayores.

La penalización es que el consumo de memoria local (en rango) está entre 2x-5x, por lo que es posible que deba usar más nodos de cálculo en su clúster de lo habitual.

Por cierto, intenté usar listas de búsqueda, pero el tiempo necesario para determinar la estructura de dispersión fue más que el ensamblaje / resolución. Pero mi implementación fue muy simple y no usé información sobre bordes.

La otra opción es usar rutinas como DMMeshCreateExodus como se muestra en este ejemplo.

stali
fuente
0

Está buscando enumerar todas las conexiones únicas (gi, gj), lo que sugiere colocarlas todas en un contenedor asociativo (no duplicado) y luego contar su cardinalidad; en C ++ esto sería un std :: set <std :: pair <int, int>>. En su pseudocódigo, reemplazaría "nnz [i] ++" con "s.insert [pair (gi, gj)]", y luego el número final de nonzeros es s.size (). Debe ejecutarse en tiempo O (n-log-n), donde n es el número de nonzeros.

Como probablemente ya conozca el rango de posibles gi, puede "separar" la tabla según el índice gi para mejorar el rendimiento. Esto reemplaza su conjunto con un std :: vector <std :: set <int>>. Rellena eso con "v [gi] .insert (gj)", luego el número total de nonzeros viene de sumar v [gi] .size () para todos los gi's. Esto debería ejecutarse en el tiempo O (n-log-k), donde k es el número de incógnitas por elemento (seis para usted, esencialmente una constante para la mayoría de los códigos pde, a menos que esté hablando de métodos hp).

(Nota: quería que esto fuera un comentario sobre la respuesta seleccionada, pero fue demasiado largo, ¡lo siento!)

rchilton1980
fuente
0

miT×

miyojT={1yoF reoF jmilmimetrominortet yo0 0milsmiwhmirmi
Matriz UN=mimiTtiene el patrón de dispersión que estás buscando. Tenga en cuenta que implementarmiT es más fácil, es por eso que definí miT en lugar de mi.
Nicola Cavallini
fuente