You are currently browsing the category archive for the ‘Informática’ category.

Llego ya un tiempo implementando los diferentes esquemas multigrid en C++ y, tras un periodo de larga oscuridad, por fin aparece un poco de luz: ayer , por primera vez, pareció funcionar todo (en una dimensión y con fronteras Dirichlet, claro… Aunque todo lo demás ya está también implementado).

En el primer nivel necesitamos un esquema de relajación. Esta implementado un weighted Jacobi, por su, a mi humilde entender, natural paralelización en CUDA (y que cuando w=1 tenemos un Jacobi), un SOR (sobrerelajación, que con w=1 deriva en un Gauss-Seidel), que suele ser la opción de siempre y un weighted red-black Gauss-Seidel, de cara a paralelizaciones tradicionales (OpenMP, MPI).

En el segundo nivel tenemos los algoritmos de corrección con esquemas V-Cycle, en iterativo, y \mu-Cycle, en recursivo.

Finalmente, tenemos el FMG en el último nivel, tambien en iterativo y en recursivo. En total, 12 combinaciones posibles (en realidad, doce para una dimensión, doce para dos dimensiones y doce para tres dimensiones)

A ver que tal se dan hoy las ejecuciones en dos dimensiones.

Anuncios

Finalmente, tenemos ya varias clases abstractas que instanciamos mediante la creación de algunas de sus clases concretas hijas en función de un fichero de configuración.

Algunas de estas clases abstractas son: OdeSolver, SpatialDecomposition, PdeSolver, Kernel, EoS, Equation, Display que pueden ser concretadas en alguna de sus clases herederas (por ejemplo SpatialDecomposition podria ser instanciada como AllPair, como LinkedList o como cualquiera que implementemos posteriormente).

A nivel de código, lo que nos encontramos es lo siguiente:

  1. En el fichero de configuración aparece algo como SpatialDecomposition=LinkedList o <SpatialDecomposition>LinkedList</SpatialDecomposition>, en función del formato que tenga nuestro fichero de configuración
  2. Este fichero lo lee una clase Configuracion que posteriormente es capaz de suministrar esta información.
  3. En algun lugar de nuestro código tenemos SpatialDecomposition *spatialDecomposition y una composición alternativa de acciones donde en una de las ramas, la que se corresponde a la información que nos suministra Configuración, con spatialDecomposition = new LinkedList(...)
  4. En la clase abstracta SpatialDecomposition tenemos un método virtual getNNP que implementan todos sus descendientes, de manera que en LinkedList tenemos su correspondiente implementación para esta clases concreta.
  5. Cuando realizamos la invocación del método spatialDecomposition->getNNP(...) (-> en lugar de . porque tenemos un puntero a la clase y no la clase) este siempre tendrá este formato independientemente de la clase concreta que tengamos en un momento determinado por debajo, por lo que si posteriormente decidimos utilizar AllPair, la llamada anterior queda de la misma manera.

Esto permite que exista una capa de abstracción de manera que el conjunto principal de clases trabaja invocando a métodos virtuales de clases abstractas que se transforman en llamadas a métodos concretos de la clases descendiente instanciada de manera trasparente.

Toda aplicación es habitual que tenga un fichero de configuración. Lo primero que hace el programa cuando empieza a funcionar es leer este fichero en el que se especifican una serie de parámetros que determinan una cierta configuración de funcionamiento.

Cuando somos nosotros los que estamos desarrollando una aplicación, la pregunta que se nos pasa por la cabeza es: ¿Qué formato debe tener un fichero de configuración? Ciertamente, dado que nosotros somos los desarrolladores, tenemos total libertad para determinar cual sera éste. Sin embargo, el hecho de seleccionar uno u otro nos permitirá aproximar nuestra aplicación a la manera estándar de funcionar el software, teniendo también la posibilidad de utilizar herramientas desarrolladas para estos estándares de facto.

Volviendo a nuestra libertad de elección, la primera manera que se nos ocurre es tener un fichero de texto con un formato determinado. Lo único que tenemos que hacer es desarrollar un código capaz de leer y escribir ficheros con este formato.

De unos años a esta parte, los formatos mas utilizados para los ficheros de configuración son los ficheros INI, XML, YAML y JSON.

INI: es el mas sencillo. Solo admite dos niveles y no esta preparado para binarios.

;config valSPH
...
numParticles=10000
kernel=cubic
odeSolver=rungeKutta23
...

XML: muy extendido. Soporta múltiples niveles, binarios… Hay muchas herramientas para parsearlo, por ejemplo Xerces de Apache, o también es fácil programarse uno basado en SAX.


<!-- config valSPH -->
...
<numParticles>10000</numParticles>
<kernel>cubic</cubic>
<odeSolver>rungeKutta23<odeSolver>
...

JSON: Dispone de MIME type, “application/json” con codificación y decodificación nativa en navegadores. Muy utilizado en projectos Ajax.

{

...
"numParticles": 10000,
"kernel": "cubic",
"odeSolver": "rungeKutta23",
...

}

JAML: nace como una generalización de JSON. Existe soporte en C++ con yaml-cpp

---# config valSPH
...
numParticles:  10000
kernel:        cubic
odeSolver:     rungeKutta23
...

Actualizamos la minianimación que teniamos incluyendo los algoritmos y las estructuras de datos necesarias para la NNPS.

Con el fin de poder comprobar la determinación de vecinos, el color de cada péndulo depende, en un instante dado, del número de vecinos que tiene a una distancia menor o igual a h.

En la siguiente animación se utiliza una LinkedList:

En el método SPH necesitamos conocer cual es su conjunto de vecinos de una partícula a. Este viene determinado por la smoothing length h, uno de los parámetros de las funciones kernel.

Supongamos que tenemos N partículas. Si N es lo suficientemente pequeño, lo único que tenemos que hacer es, para cada partícula, recorrer el resto de partículas calculando su distancia a nuestra partícula y tener en consideración solo aquellas que esten a distancia menor que h (all-pair search). La complejidad del algorítmo es O(N^2).

Sin embargo, cuando el número de partículas crece, y que lo ha de hacer pues en eso se basa la potencia del método, esta manera de trabajar es mejorable.

A continuación veremos algunas opciones, pero la idea general es que necesitamos tener una descomposición espacial de nuestro escenario que nos permita encontrar de manera eficiente el vecindario de cada una de nuestras partículas y que tendremos que mantener actualizada a medida que evoluciona el sistema.

Otra opción es la linked-list. Se trata de superponer una malla con un tamaño de celda 2h, de manera que, dada una partícula, solo tendremos que buscar las partículas que se encuentran en las celdas adyacentes. En C++ tenemos las clases list y vector. La primera por nombre y la segunda porque la clase vector puede crecer dinámicamente. Solo podremos utilizarlas si la h es constante para todas las partículas. La complejidad, con un número suficientemente pequeño de partículas por celda, es O(N).

Finalmente, podemos trabajar con árboles. Los árboles permiten complejidades genéricas en búsquedas de O(N \log N) y permiten trabajar con h diferentes para cada partícula. En astrofísica, por ejemplo, podríamos tener un arbol binario de búsqueda, pues tenemos que simular objetos autogravitantes y necesitamos resolver un problema de n-cuerpos, que parece ser que es lo que mas tiempo de CPU consume. Para hacer esto de manera eficiente necesitamos un BST. Se trata de utilizar este mismo para realizar las búsquedas. La clase map de C++ se implementa con un arbol binario balanceado (AVL).

Existen otras estructuras de datos que permiten hacer descomposiones espaciales que podria ser interesante analizar (UB-tree, Octrees, BST, kd-tree, R-tree).

En la siguiente simulación tenemos 1000 partículas. Cada partícula a representa un péndulo de masa m_a y posición (x_a(t),y_a(t),z_a(t)), con z_a(t) constante, sometido a las fuerzas:

  1. F_1 = -m_ag, la fuerza de la gravedad, con g constante.
  2. F_2, la fuerza elática de la suspensión, de sentido opuesta a la posición del péndulo y de magnitud k_1d, con k_1 > 0 y d la variación de la longitud del péndulo respecto de su longitud en reposo l.
  3. F_3, la fuerza de fricción, con sentido opuesta a la velocidad del péndulo y magnitud k_2v, con v = \sqrt{(x_a')^2+(y_a')^2} y k_2 > 0.

La ley de Newton F = ma queda, para cada uno de los péndulos, es decir, para cada partícula, como el sistema de dos ecuaciones diferenciales de segundo orden:

m_a \begin{bmatrix} x_a''(t) \\ y_a''(t) \end{bmatrix} = F_1 + F_2 + F_3

que es:

m_ax_a'' = -k_1 (1-\frac{l}{\sqrt{x_a^2+y_a^2}})x_a - k_2\frac{x'_a}{\sqrt{x_a'^2+y_a'^2}}

m_ay_a'' = -k_1 (1-\frac{l}{\sqrt{x_a^2+y_a^2}})y_a - k_2\frac{y'_a}{\sqrt{x_a'^2+y_a'^2}}

Podemos expresar este sistema como un sistemad de ecuaciones de primer orden introduciendo incongnitas adicionales: u_a = x_a' y v_a = y_a', con lo que nos queda:

\left \{  \begin{array}{rcl}  x_a' &=& u_a \\  y_a' &=& v_a \\  m_au_a' & = & k_1 (\frac{l}{\sqrt{x_a^2+y_a^2}}-1)x_a - k_2\frac{u_a}{\sqrt{u_a^2+v_a^2}} \\  m_av_a' & = & k_1 (\frac{l}{\sqrt{x_a^2+y_a^2}}-1)y_a - k_2\frac{v_a}{\sqrt{u_a^2+v_a^2}} - m_ag \\  \end{array}  \right .

En la siguiente simulación tomamos m_a aleatoria para cada partícula a, por lo que tendremos que resolver el sistema de EDOs numéricamente para cada una de ellas. Además, tendremos l=1, k_1 = 10000 y k_2=1 en todos los péndulos. Cada uno de éstos se distribuye a lo largo de una espilar cilíndrica y las velocidades iniciales son, para todas ellas, u_{a_0}=0, v_{a_0} = -0.7 y v_{z_0} = 0. El intervalo de tiempo es desde t_i = 0 hasta t_f=10 que se divide en 400 subintervalos.

El resultado es el siguiente:

Para poder generar los call graph y los caller graph desde doxygen necesitamos tener instalado Graphviz.

Aquí tenemos la direccion desde donde nos lo podemos bajar. En el caso de mac, es un pkg que instala el fichero binario dot en el directorio /src/local/bin. Tendremos que añadir este path en la variable DOT_PATH en la pestaña Expert de doxygen.

A continuación, unos ejemplos del tipo de diagramas generados:

Además, en la segunda captura podemos observar que las clases pueden mostrarse con estilo UML.

A medida que codificamos y probamos nuestras clases las iremos documentando mediante doxygen.

La instalación y utilización básica de doxygen es extremadamente sencilla. El programa está disponible aquí.

A continuación tenemos un ejemplo del formato de los comentarios añadidos y el tipo de salida generado.

A continuación nuestra primera animación creada con VisIt a partir de ficheros silo generados a partir de llamadas a métodos de nuestras primeras clases.

Temporalmente, aunque es mejor utilizar nuestras partículas simplemente como nodos de interpolación a la hora de visualizar, las mostraremos como puntos.

A partir de este momento, podremos tener una referencia visual de nuestras partículas que nos ayudará mucho a la hora de evaluar como de bien estamos haciendo las cosas.

Sin mas dilación, nuestra minianimación:

No es mas que un conjunto de mil partículas desplazandose sobre un cilindro. La animación consta de 100 ficheros .silo y hemos generado el mpeg con el wizard de VisIt.

Ya dedicamos un post a VisIt. En el creamos una animación a partir de un conjunto de ficheros existentes.

¿Como generamos ficheros .silo desde nuestro código? En primer lugar, necesitamos tener las librerias correspondientes, que las podemos conseguir ejecutando:

./build_visit --console --no-visit --no-thirdparty  --thirdparty-path /usr/local --silo --hdf5 --szip

desde el terminal, y donde build_visit es un fichero que podemos conseguir aqui.

A continuación, necesitamos incluir la libreria <silo.h> en nuestro fichero y añadir:

PLATFORM=i386-apple-darwin10_gcc-4.2

SZIP_DIR=/usr/local/szip/2.1/$(PLATFORM)
SZIP_CPPFLAGS=-I$(SZIP_DIR)/include
SZIP_LDFLAGS=-L$(SZIP_DIR)/lib
SZIP_LIBS=-lsz

HDF5_DIR=/usr/local/hdf5/1.8.4/$(PLATFORM)
HDF5_CPPFLAGS=-I$(HDF5_DIR)/include $(SZIP_CPPFLAGS)
HDF5_LDFLAGS=-L$(HDF5_DIR)/lib $(SZIP_LDFLAGS)
HDF5_LIBS=-lhdf5 $(SZIP_LIBS) -lz

SILO_DIR=/usr/local/silo/4.6.2/$(PLATFORM)
SILO_CPPFLAGS=-I$(SILO_DIR)/include $(HDF5_CPPFLAGS)
SILO_LDFLAGS=-L$(SILO_DIR)/lib $(HDF5_LDFLAGS)
SILO_LIBS=-lsiloh5 $(HDF5_LIBS) -lm

LDFLAGS=$(LDFLAGS) $(SILO_LDFLAGS)
LIBS=$(SILO_LIBS)
CPPFLAGS=$(CPPFLAGS) $(SILO_CPPFLAGS)

a nuestro Makefile

Cuando eramos niños, nos pasabamos el curso esperando la llegada de las vacaciones. Sin embargo, a medida que pasaban, cada vez tenías mas ganas de volver a las clases.

En esto de programar pasa un poco lo mismo. Cuando estas metido en un proyecto en el que te pasas dias y dias escribiendo, probando código y peleandote con los ordenadores, lo que mas desea uno en el mundo es acabar con aquello. Sin embargo, una vez terminado y pasado un tiempo, después de disfrutar de un merecido descanso y del placer y de la potencia de ver aquello funcionando, nos entra el gusanillo de volver a la carga.

Existen muchos modelos para el ciclo de vida del software, es decir, diferentes maneras de enfrentarte al reto de como desarrollar sofware. Sin embargo, con el tiempo y despues de acumular muchas “horas de vuelo”, uno ya tiene mas o menos definida su pequeña estrategia. Son cosas de sentido común, pero que no va mal tenerlas presente.

Para empezar, es muy importante tener una visión global del sistema, de manera que, nuestro primer diseño, es interesante que lo abarque todo. Para ello van bien, por ejemplo, realizar el diagrama de casos de uso, el diagrama de clases, y algunos diagramas de comunicación y de secuencia. Además, existe software que sera capaz de generarnos, a partir de ellos, la arquitectura de la aplicación, es decir, el conjunto de todos los ficheros de clases que necesitaremos y la interrelación entre ellos.

Sin embargo, en el momento de empezar a implementar el software, a escribir el programa, es mejor seguir una estrategia en espiral.

cuya idea de fondo es sencilla: no pretender programarlo todo de golpe sino hacerlo de manera incremental. Para ello, empezamos seleccionando un pequeño conjunto del global de los objetivos de nuestro software, seleccionaremos las clases que nos permitiran asumirlos, las implementaremos y las probaremos. Llegados a este punto habremos dado una vuelta en la espiral ya que estaremos en condiciones de volver a empezar la misma rutina con otro pequeño conjunto de objetivos pero con la diferencia de que nuestro programa habrá crecido con respecto a la vuelta anterior y lo que allí nos propusimos estará funcionando.

Pues nada, teniendo ésto presente y sin mas preámbulos, nos ponemos el mono de programador y, por fin, ¡a programar!

Ayer asistí al primer Friday’s miniWorkshop del IVICFA (L’institut Valencià d’Investigació Coorporativa de Física Avançada) que trataba sobre supercomputación y computación GRID. Forma parte de un ciclo de seminarios sobre “Fronteras de la Física” y contaba ayer, entre otros atractivos, con la apertura de los mismo por parte de Ian Bird, Project Leader of the Worldwide LHC Computing GRID (WLCG), con “Petabyte scale computing for the LHC”, o el seminario “Modeling complex solar magnetodynamical phenomena using supercomputing and visualization techniques” de Fernando Moreno Insertis, del Instituto de Astrofísica de Canarias (IAC) y PI of the European Solaire Network.

Aunque ya hicimos algunos comentarios en un post anterior, aprovechamos la ocasión para volver a hablar sobre estos temas y así aclararnos las ideas.

Un computador computa, por lo que un supercomputador supercomputa ;-). Como el elemento fundamental para computar en la arquitectura Von Neumann son las CPUs, en un supercomputador dispondremos de multiples de éstas. El otro elemento fundamental en la arquitectura Von Neumann, que es la que las convierte en máquinas de proposito general, es la memoria. En función de como las CPUs comparten esta memoria, nos encontramos con dos modelos de supercomputación: memoria compartida y memoria distribuida. Las dos interfaces que nos permiten programar en estos ambientes son OpenMP y MPI respectivamente.

Tenemos que aclarar que, cuando hablamos de supercomputación, nos estamos refiriendo a que estas entidades, múltiples procesadores y memorias, existen físicamente, pues podemos encontrarnos todos estos elementos de manera virtual en ordenadores mas sencillos con sistemas operativos multiproceso.

La última tendencia en arquitecturas paralelas son las arquitecturas vectoriales, arquitecturas SIMD: una instrucción múltiples datos, debido a su enorme desarrollo en la evolución de las tarjetas gráficas, que es donde aparecen de manera natural al tener que aplicar la misma operación a múltiples píxeles. CUDA es un estandar de facto en la extensión de estas operaciones a cualquier tipo de datos no necesariamente gráficos.

Por tanto, en el mundo de los supercomputadores actuales es fácil encontrarnos con arquitecutras paralelas heterogeneas en donde conviven simultanemente la memoria compartida con la memoria distribuida de múltiples unidades de proceso que soportan un juego de instrucciones vectorial. La paralelización de estos códigos se realiza ad-hoc, pero, al igual que sucedió con el triunfo de las arquitecturas RISC sobre las CISC, la última palabra es posible que la tengan los compiladores, aunque es un hecho objetivo que la paralelización automática es instrínsecamente muy compleja.

Un compilador, aunque se suele asociar a los traductores de lenguajes de alto nivel a lenguaje máquina, en realidad son traductores entre dos lenguajes, sean del tipo que sean, por lo que es totalmente lógico pensar en compiladores de programas secuenciales en un lenguaje determinado a programas paralelos heterogeneos en el mismo lenguaje. Mientras se programó en lenguaje máquina, las arquitecturas CISC dominaron el mercado. Con la aparición de los lenguajes de alto nivel y de compiladores potentes para los mismos que solo utilizaban un subconjunto muy reducido del amplísimo conjunto de instrucciones disponibles, entraron en escena las arquitecturas RISC y desplazaron a las primeros.

Por otra parte, ¿cúal es la propuesta del GRID computing? Pués su aparición es, al igual que por ejemplo los protocolos TCP/IP de Internet, una solución de facto a un problema existente y es, por una parte, el hecho de que existan en un momento determinado multitud de recursos repartidos a lo largo del planeta, y por otra, a la posibilidad de compartirlos de manera transparente por multitud de usuarios igualmente dispersos.

Por ejemplo, la realidad es que un grupo de la Universidad de las Palmas de Gran Canaria tiene un supercomputador fruto de sus necesidades en un determinado momento y otro grupo de la University of Tasmania tiene otro con los mismos recursos. Si en un momento dado se dan cuenta de que podrían compartir sus recursos, lo cual a priori siempre es positivo desde el punto de vista de la teoría de juegos, la tecnología grid ofrece una capa de abstracción, la middleware grid, gracias a la cual pasamos a tener un único sistema con el total de los recursos.

Pensando trivialmente, solo por aclarar ideas, y sabiendo que es un caso totalmente irreal, si ambas máquinas solo se utilizasen durante las ocho horas laborables locales, supongamos de 8h a 18h, obviamente los dos grupos salen muy beneficiados, pués con una diferencia horaria de 10 horas no habría conflictos de acceso y los dos pasarían a disponer del doble de recursos de los que tenían.

Por tanto, los supercomputadores existen y existirán, pues son la mejor solución a nivel local, pero la tecnología GRID, sin entrar en consideraciones sobre Cloud, es la mejor solución a nivel global, ya que permite la interconexión transparente de estos óptimos locales que pueden llegar a ser muy heterogéneos.

El Dr. Treb Lae y la Dra. Aini Vadh simulan colisiones de objetos compactos. Para poder hacerlas es para lo que me utilizan. Mi nombre es Tnala y soy una supercomputadora. Pero no una cualquiera, no, soy una de las que aparece en el TOP500.

—Vamos a hacer otra simulación —dice Treb—. ¿Qué colocamos esta vez?

—Probemos con una estrella de neutrones y un agujero negro. Pero vamos a cambiar algunas cosillas —contesta Aini.

Coge el ratón, se inclina sobre la pantalla y empieza a trabajar. Para empezar, distribuye los objetos sobre el escenario de la simulación, a continuación, define los parámetros que caracterizan a cada uno de los objetos compactos y, finalmente, modifica algunas cuestiones referentes a la propia simulación. Pero cuando ya parece que lo tiene, se reclina sobre la silla y se queda pensando. No lo tiene claro. Casi siempre, antes de empezar cualquier simulación, cambian una y otra vez las cosas. Es por eso que, hasta que no empiezo a simular, no pierdo el tiempo creado una y otra vez los diferentes objetos. Simplemente me guardo la información para luego.

—Creo que será mejor trabajar solo con estrellas —afirma finalmente Aini con determinación—. Probemos con dos estrellas con estas ecuaciones de estado, ¿te parece?

Treb se lo piensa. Tras el silencio esboza una leve sonrisa. Acaba de entender lo que pretende Aini.

—Perfecto. Creo que esta vez si que lo conseguimos —contesta feliz—. ¡Eres realmente brillante!

Aini sonríe contenta e inmediatamente lanza la simulación.

—¡Ya está! —exclama—. Vamonos. A ver si mañana tenemos buenas noticias.

Se marchan a casa a dencansar y me dejan. Yo no necesito descansar. Ahora es cuando me pongo manos a la obra.

Lo primero que hago es crear el escenario y colocar los objetos tal y como me lo han definido, para lo que utilizo la información que me he guardado previamente. A partir de ahora si que habrá cuerpos compactos creados de verdad. Inicializo el campo gravitatorio (una malla con nodos para la parte elíptica), añadiendo los posibles BH, e inicializó los fluidos de las NS, SS o DW (las partículas para la parte hiperbólica) junto con la descomposición espacial para las relaciones de partículas vecinas. Me guardo el estado inicial.

Ahora si que empiezo con la simulación, que es un vaivén entre la parte elíptica y la hiperbólica, es decir, miraré como el fluido me deforma el espacio-tiempo, desplazaré el fluido según esa deformación y volveré buscar como esa nueva distribución del fluido me modifica nuevamente el espacio-tiempo y vuelta a empezar.

Técnicamente, primero resuelvo el sistema elíptico para el paso de tiempo actual con lo que obtengo una métrica y una curvatura que necesitaré conocer para desplazar las partículas del fluido, que seguirán geodésicas. A continuación tengo que desplazar el fluido. Necesito conocer la densidad, la presión y la velocidad que recuperaré de la densidad, el momento y la energía. Para cada partícula calculo el valor de sus variables conservadas, para lo que necesito sus vecinas, el kernel, las eos y el solve rk4. Lo ideal sería tener un sistema que me resuelva esto para todas las partículas de forma simultánea (¿se puede?) y que en un futuro paralelizaré. Una vez resuelto, desplazo las partículas, actualizo sus vecinos y muevo los agujeros. Almaceno el estado. Finalmente, y antes de volver a empezar, revisó si el paso de tiempo es el apropiado (la simulación de la perdida de momento y de energía fruto de la radiación gravitacional en forma de ondas gravitatorias, así como la viscosidad y la conductividad térmica ya están incluidas en las ecuaciones discretizadas que usamos?).

Hago esto tantas veces como me han configurado, de manera que, en cuanto acabo, tengo almacenados el estado de la simulación en cada instante de tiempo, por lo que Treb i Aini podran visualizarlo y estudiarlo. Creo que el resultado si que es el que se esperaban. Me alegro por ellos (si es que puede una máquina alegrarse :-)).

La siguiente figura muestra un diagrama en el que se ve como se han comunicado los diferentes objetos:

A proposito, ¿sabeis a quién hacen honor los nombres de Treb Lae, Aini Vadh y Tnala? Envia un comentario con la respuesta correcta y participarás en el sorteo… 🙂

El software DualSPHysics es la versión paralela con CPUs y GPUs de SPHysics. El código se ha implementado esta vez en C++ y CUDA.

A continuación mostramos dos diagramas de colaboración de DualSPHysics generados automáticamente por doxygen,

Mostramos como, comentando correctamente nuestro diseño, Visual Paradigm for UML nos genera reports en PDF o HTML con documentación sobre nuestra aplicación.

A continuación mostramos un pequeño fragmento de como queda el HTML:

Otro diagrama disponible en UML 2 es el diagrama de comunicación.

El diagrama de comunicación permite modelar la interacción entre los diferentes objetos que se produce mediante mensajes en secuencia, es decir, muestra que mensajes se pasan los objetos entre si y en que orden. Es un diagrama muy útil, pues muestra tanto información estática, tomada del diagrama de clases, como información dinámica, tomada del diagrama de casos de uso y del  diagrama de secuencia.

A continuación muestro el diagrama de comunicación que he realizado mediante Visual Paradigm for UML de SPHysics:

Técnicamente, tendríamos que hablar de un pseudodiagrama de comunicación, pués el lenguaje de programción es fortran que no trabaja con orientación a objetos, por lo que en lugar de clases  tenemos módulos. Sin embargo, y salvando las distancias, cumple sobradamente su propósito semántico.

Una vez un profesor nos dijo algo así como: “No programeis lo que ya esta programado”, una especie de versión light de la reinvención de la rueda. Lo que quería decir era, sencillamente, que antes de empezar a diseñar algo desde cero para resolver un problema teniamos que investigar si ya existia un software capaz de hacerlo. Si la respuesta era negativa, adelante, pero si la respuesta era positiva, teniamos que plantearnos una serie de preguntas: ¿Es gratuito o hay que pagar por él? ¿Se adapta perfectamente a mis necesidades o necesita cierto grado de adaptación? ¿Tengo acceso al código fuente y tengo permiso para modificarlo a mi medida? ¿Que piensa el cliente al respecto? etc.

En el caso que nos ocupa, un sofware que promete es SPHysics, un esfuerzo conjunto de investigadores en la Johns Hopkins University (U.S.A.), la University of Vigo (Spain), la University of Manchester (U.K.) y la University of Rome La Sapienza (Italy). Evidentemente, se trata de hidrodinámica newtoniana y no magnetohidrodinámica relativista, por lo que hay un salto cualitativo en la física considerada, además de que se trata de programación modular y no orientada a objetos. Sin embargo, al ser de código abierto y disponer de versión paralela sobre CUDA, nos puede servir de mucha ayuda a la hora de implementar muchas cosas.

A continuación se enlazan dos simulaciones con 100M y 226M partículas en las que se puede apreciar la potencia del software:

Junto al diagrama de clases, que forma parte de los diagramas de modelado estructurado (visión estática del modelo) que ya hemos comentado, existen otros diagramas de modelado de comportamiento (visión dinámica del modelo) ampliamente utilizados como son los diagramas de casos de uso y los diagramas de secuencia.

El diagrama de casos de uso muestra cada una de funcionalidades del sistema modelado en una visión de caja negra de las mismas. Representa al sistema como un rectángulo que contiene tantas elipses como casos de uso diferentes junto con el actor o actores con los que interactuará.

Un ejemplo diagrama de casos de uso de nuestro sistema SPH con un solo caso de uso Simulation podria ser:

El diagrama de secuencia permite modelar cada caso de uso y describe la interacción entre los diferentes objetos de un sistema a través del tiempo para la consecución del mismo. Es por tanto una visión de caja blanca. Se representan los diferentes objetos como rectangulos o conjuntos de rectangulos en función de si son simples o multiples junto con flechas entre los objetos que representan los mensajes entre estos.

Un posible diagrama de secuencia del caso de uso Simulation podria ser:

VisIt es una herramienta open source que nos permite visualizar y analizar conjuntos de datos extremadamente grandes, del orden de tera y peta, en multiples plataformas. Podemos visualizar rápidamente nuestros datos, animarlos, manipularlos y almacenar los resultados obtenidos.

Algunas características interesantes son:

  • Tipos estandar de gráficos: Curve plot, Mesh plot, Contour plot, Surface plot, Vector plot,  Tensor plot, Volume plot, etc.
  • Podemos trabajar en 1D, 2D, 3D y variando en tiempo.
  • Permite definir diferentes tipos de mallas: rectilineas, curvilineas, desestructuradas, puntuales y AMR (Adaptive Mesh Refinement), etc.
  • Manipulación de datos (slicing, clipping, project, etc.) e interrogaciones (analisis comparativo, debugging, etc.).
  • Opciones para anotaciones, iluminación y rendering.
  • Podemos trabajar con datos escalares, vectoriales y tensoriales.
  • Permite paralelizaciones.

Para visualizar los datos podemos utilizar VisIt como aplicación mediante fichero (\approx 100 formatos diferentes), base de datos (se pueden desarrollar nuevos plug-ins) o como librería mediante código. También podemos crear animaciones mediante flipbook, keyframing o scripting. Podemos trabajar en local, en remoto o utilizando la arquitectura cliente/servidor.

VisIt se compone de cuatro componentes:

  • Graphical User Interface (GUI): se ejecuta en local y permite, entre otras cosas, seleccionar los ficheros, crear graficos, fijar atributos, etc.
  • Viewer: se lanza en local y es donde se muestran las visualizaciones con las que podemos interactuar con el ratón.
  • Database server: puede lanzarse en remoto y permite el acceso a los datos.
  • Compute engine / Parallel compute server: puede lanzarse en remoto y es el encargado de generar los gráficos.

A continuación una pequeña animación flipbook que hemos creado con datos que ofrece VisIt en su documentación.

Los ficheros .visit, en este caso wave.visit, son ficheros de texto que contienen los nombres de todos los ficheros que guardan, cada uno, el estado de la simulación en un instante de tiempo determinado: wave0010.silo, wave0010.silo, ..., wave0700.silo. En este caso son .silo que es un formato propio de VisIt.

A continuación se muestra una imagen con todos los fichero generados de forma automática:

y el código que contienen algunos de estos ficheros:

Cada instanciación de una clase define un objeto: Persona es una clase, Jose es un objeto, una instancia de Persona. La clase es una plantilla que permite crear objetos.

Una clase almacena un estado: un conjunto de atributos que guardan información del objeto; y un comportamiento: un conjunto de métodos que nos permiten trabajar con el objeto.

A continuación, nuestro diagrama de clases con una muestra de atributos, métodos y relaciones:

Cuando modelamos sistemas, ciertos componentes pueden estar relacionados con otros y estas interrelaciones deben ser modeladas. La relación de asociación es el tipo de relación básico entre dos clases. Se representa mediante una linea donde podemos incluir roles, cardinalidades, dirección y restricciones. Suele implementarse como variables de instancia de una clase en otra.

Con respecto a la implementacion, podriamos tener algo como:

//asociación: obj1 y obj2 tienen una relación.
public class Obj1 { void func(Obj2 obj2) {} };

Una relación de agregación es un tipo particular de asociación aparecido después y que permite describir cuando una clase está formada por otras clases. En las agregaciones, el ciclo de vida de una parte es independiente del ciclo de vida del todo, es decir, que si el padre se elimina, no se eliminan sus hijos. En el diagrama aparecen con una punta de flecha en forma de diamante negro en la clase padre. El código podría ser:

//agregación: obj1 posee a obj2 que se lo han prestado: cuando obj1 muere obj2 puede vivir.
public class Obj1 { private Obj2 obj2; Obj1(Obj2 obj2) { this.obj2 = obj2; } }

Una relación de composición permite describir el mismo tipo de relación que las agregaciones pero, por el contrario, la dependencia de sus ciclos de vida es mas fuerte: si el padre se elimina también se eliminan todos sus hijos, aunque una parte puede ser eliminada sin eliminar el todo. En el diagrama aparecen con una punta de flecha en forma de diamante blanco en la clase padre. En código:

//composición: obj1 posee a obj2 y es responsable de su tiempo de vida: cuando obj1 muere obj2 también muere.
public class Obj1 { private Obj2 obj2 = new Obj2(); }

La lista de clases obtenida junto con sus relaciones de generalización:

Imagen

(Diagrama realizado con Visual Paradigm for UML)

El diagrama de clases muestra los bloques con los que construiremos un sistema orientado a objetos. Describen la vida estática del modelo, mostrando que atributos y comportamientos ocurren sin entrar a detallar de que manera se realizan. También muestran las relaciones existentes entre las clases: generalizaciones que reflejan herencias, agregraciones para reflejar composición o uso y asociaciones para mostrar conexiones.

Para representar cada clase utilizamos un rectángulo con tres compartimentos. En el primero va el nombre de la clases, en el segundo sus atributos que pueden ir acompañados de su valor inicial y en el último los métodos junto a sus parámetros. La visibilidad se indica mediante los simbolos – para indicar que un atributo o método es privado, y + para indicar que un atributo o un método es público.

Una relación de generalización indica herencia. Se representa mediante una punta de flecha triangular en la clase padre que generaliza una clase hijo. Un objeto instanciado de la clase hijo heredará de manera implícita los atributos y métodos de su clase padre. En nombre de las clases abstractas va en cursiva.

Volviendo a nuestro diagrama particular, las clases abstractas las hemos indicado temporalmente mediante un borde discontinuo y los colores indican posibles paquetes: verde para clases específicas de SPH, amarillo para numericalMethods, azul para astrophysics y rojo para dataStructures.

Vamos a hacer una lista con todas las clases que creemos que vamos a necesitar para la implementación del método SPH y aplicarlo a simulaciones de problemas astrofísicos. A continuación pensaremos en sus atributos y métodos. Finalmente, interrelacionaremos las clases entre si. Para todo ello, nos apoyaremos en el Visual Paradigm. Empecemos:

  1. Dispondremos de un stage donde ocurre la acción de nuestra simulación. Este contendrá una clase abstracta spatialDecomposition que podremos implementar posteriormente en las clases derivadas bsptree, octree, kdtree, linkedList y que utilizaremos para selección de partículas vecinas incluso con h variable. También necesitamos una clase mesh/grid para ir resolviendo las ecuaciones de campo de Einstein, que constituyen un problema PDE elíptico, e ir obteniendo el campo gravitacional en el que se mueve nuestro fluido. Por tanto, también necesitaremos un ellipticSolver que derivaremos en spectralSolver o finiteDifferenceSolver.
  2. Necesitamos una clases fluid que almacenará toda la información referente a un fluido .
  3. Los fluidos nos permitiran modelar compactObjects como blackHole, neutronStar, strangeStar… y en donde dispondremos de una operacion merge.
  4. Y también la clase particle que permitirá almacenar la información de cada una de las partículas de nuestros fluidos.
  5. La clase kernel nos permitirá evaluar la función kernel. Como ya comentamos en un post, para ofrecer la posibilidad de utilizar diferentes funciones kernel, la clase kernel será abstracta ofreciendo métodos virtuales que implementaremos en las clases derivadas gaussianKernel, quadraticKernel, cubicKernel, quinticKernel, tensorialKernel.
  6. Necesitamos también una clase odeSolver que utilizaremos para resolver las distintas ODE que aparecen al “discretizar” las ecuaciones de la hidrodinámica. Como tenemos diferentes posibilidades, la implementaremos en una clase abstracta cuyos métodos virtuales los implementaran rungeKutta2OdeSolver, rungeKutta4OdeSolver, velocityStormerVerletOdeSolver, leapfrogOdeSolver, modifiedEulerOdeSolver y que nos permitirá utilizar cualquiera de ellos indistintamente.
  7. La clase abstracta eos nos permitirá trabajar con diferentes ecuaciones de estado.
  8. Desde el punto de vista matemático, necesitamos definir una clase tensor y sus casos particulares scalar, vector y matrix.

En C++ podemos definir clases abstractas. Son clases pensadas para definir un comportamiento, es decir, un conjunto de métodos sin implementación. Sus clases derivadas están obligadas a implementar estos métodos, siempre i cuando queramos que sean clases concretas instanciables. Los métodos sin implementación de las clases abstractas reciben el nombre de métodos virtuales.

En el siguiente diagrama de clases UML podemos ver dos pequeños ejemplos de como podríamos utilizar las clases abstractas. En el primer caso, tenemos la clase abstracta kernel para implemetar la función kernel del método SPH. Uno de sus métodos debe devolver un valor a partir de los parámetros d y h. La clase kernel define la signatura del método virtual calcular. Las clases derivadas quadraticKernel, cubicKernel y quinticKernel lo implementan de manera diferente. La clase que lo utiliza tiene como atributo un kernel abstracto, de manera que le podremos asignar cualquiera de las derivaciones concretas. Lo mismo ocurre con la clase abstracta Eos que tiene un método que devuelve la presión. Tenemos dos clases derivadas que lo implementan, cada cual de una manera. La clase cliente con un atributo Eos podrá instanciar cualquiera de las dos clases derivadas.

El siguiente código, generado automáticamente por Visual Paradigm, muestra como queda implementado el UML2 de kernel i cubicKernel en C++:

kernel.h

namespace example {

 class kernel {

 public:
   virtual double calculate(double d, double h) = 0;

 };

}

cubicKernel.h

namespace example {

  class cubicKernel : example::kernel {

    public:
      double calculate(double d, double h);

  };

}

cubicKernel.cpp


#include "cubicKernel.h"

double example::cubicKernel::calculate(double d, double h) {

  //aquí va la implementación concreta del kernel cúbico
  throw "Not yet implemented";

}

LORENE (Langage Objet pour la RElativité NumériquE), tal y como aparece en la página web de la sección Meudon del Observatorio de Paris, es un conjunto de clases C++ pensada para resolver varios problemas que aparecen en relatividad numérica, y de manera mas amplia en astrofísica computacional. Proporciona una serie de herramientas para resolver ecuaciones en derivadas parciales mediante métodos espectrales multidominio.

Las clases de LORENE implementan estructuras básicas, como vectores y matrices, objetos matemáticos mas abstractos, como tensores, y objetos astrofísicos, como estrellas o agujeros negros.

Es un software libre distribuido bajo licencia GNU General Public License.

Utilizando software de ingenieria inversa, podemos obtener el diagrama de clases de LORENE. De esta manera, podemos ver las relaciones existentes entre estas y comprender mejor el software desarrollado.

El siguiente diagrama, obtenido mediante Visual Paradigm for UML, muestra dos subdiagramas de clases, uno relacionado con agujeros negros y otro con estrellas (algunas clases se muestran expandidas con atributos y metodos mientras que otras se muestran sin expandir):

octubre 2019
L M X J V S D
« Oct    
 123456
78910111213
14151617181920
21222324252627
28293031  
Anuncios