viernes, 2 de diciembre de 2011

Video de la Simulación con Actuadores Programados

Gracias al código Fortran agregado a los loops de simulación del CodeSaturne, pudimos agregar actuadores de plasma a los resultados anteriores.
De esta forma simulamos la actuación en dos sectores del cilindro, realizando pulsaciones de plasma con ángulo de salida tangente para ver la respuesta del sistema. Se espera que las turbulencias del cilindro desaparezcan con el plasma aplicado y vuelvan a generarse tan pronto se retire el efecto del actuador.
Durante esta simulación se agregaron algunos esquemas de actuación periódicos, oscilantes y asiméticos para poder relevar información que será usada en la identificación del sistema mediante el modelo ARX.


domingo, 13 de noviembre de 2011

Usando variables 'static' en Fortran para cargar un CSV sólo una vez

Para completar la idea de utilizar un vector desde un archivo dentro del código de code saturne es importante dar ciertas garantías de performance. La subrutina a utilizar se encuentra dentro del archivo usclim.f90 que es utilizado de forma repetitiva durante la simulación.

Una forma de garantizar que la carga del CSV se realiza una sóla vez es utilizando código global y variables globales. Lamentablemente, el archivo disponible no cuenta con las secciones comunes con lo que se supone una composición de ese código dentro del programa de simulación general.

Otra alternativa es utilizar variables en el data segment persistentes entre llamadas a la subrutina. Optamos por ese camino y utilizamos la palabra reservada SAVE para realizar un breve programa de prueba:

program read_csv_once
  implicit none
 
  call readAndPrint(1000)
  call readAndPrint(100)

end program read_csv_once

subroutine readAndPrint(test_offset)
  integer:: test_offset
  integer, parameter:: values_qty = 2
  real, save, pointer:: pValues(:)
  logical, save:: pValues_initialized= .false.

  if (.not.pValues_initialized) then
    allocate(pValues(values_qty))
    open(unit=99, file="act1.csv", action="read")
    read(99, *) pValues
    close(99)
    pValues = pValues + test_offset
    pValues_initialized = .true.
  endif

  write(*,*) "Values from file:", pValues
  write(*,*) "Values Quantity:", values_qty
end subroutine


Nuevamente tenemos una cantidad fija de valores a leer del CSV dado que por el momento es un número conocido.
Notar que el puntero estático pValues NO posee un valor por defecto definido, es por ese motivo que se utiliza pValues_initialized para controlar si se hizo la lectura del CSV o no.
El resultado de correr este programa de pruebas es:

 Values from file:   1003.6295       1003.6688    
 Values Quantity:           2
 Values from file:   1003.6295       1003.6688    
 Values Quantity:           2

Demostrando que la inicialización se realizó una vez.


Algo en fortran: leyendo un array desde CSV

Ante la preguntá de ¿qué motivos tengo para hacer eso? contesto: necesito levantar un array dinámico desde un archivo para hacer uso de los valores dentro de una simulación con code saturne.

Vamos a un código muy sencillo:

program readcsv
  implicit none
 
  call readAndPrint()
end program readcsv

subroutine readAndPrint()
  integer, parameter:: values_qty = 20
  real, pointer:: pValues(:)

  allocate(pValues(values_qty))
  open(unit=99, file="act1.csv", action="read")
  read(99, *) pValues
  close(99)

  write(*,*) "Values from file:", pValues
  write(*,*) "Values Quantity:", values_qty
end subroutine

La cantidad de valores a leer está fija por el momento. Una posible solución sería hacer una prelectura de la primer fila y contar la cantidad de caracteres ',' para luego hacer una alocación con la cantidad exacta de valores. Otra posibilidad es leer toda la fila y agregarlos al array realocando en cada iteración.

martes, 1 de noviembre de 2011

Primeros Resultados de la Simulación

Luego de muchas corridas fallidas, donde el mallado de la experiencia y los distintos parámetros numéricos fueron ajustados, llegamos a una corrida limpia de la simulación.

El CodeSaturne demostró ser un tanto complejo en su puesta a punto pero muy potente y versátil. Permite cálculos simples como la experiencia del cilindro o mucho más complejos usando nociones químicas o de termodinámica.

En este caso nos limitamos a mostrar unas imágenes del cilindro luego de alcanzar un régimen estacionario respecto de las oscilaciones de la estela. En la imagen se puede notar el desprendimiento de vórtices que se pretende controlar en el sistema de control y para el cual será necesario agregar unos actuadores al mallado del cilindro.




martes, 25 de octubre de 2011

Mallado para Simulación de la Experiencia

A fin de conseguir una simulación realista de la experiencia mediante el uso de Code Saturne armamos un mallado de la experiencia que nos permite definir la simetría en juego. Existen varias herramientas de mallado, optamos por GiD por la pequeña curva de aprendizaje y la disponibilidad de información dentro del laboratorio. Como agregado este software tiene la posibilidad de hacer simulaciones con y la posibilidad de hacer simulaciones con módulos codificados por colaboradores del laboratorio.
En este caso no utilizaremos GiD para simular o realizar post-procesamiento sino simplemente para el mallado de la experiencia.
La simetría del experimento es muy sencilla y los mallados preliminares no fueron problemáticos. A continuación se incluyen distintos acercamientos de la experiencia, donde se puede observar el cilindro sobre el lado izquierdo dejando buena parte del mallado derecho para la estela que debe producir.







A lo largo de las imágenes se puede observar que el grado de precisión necesario para los nodos del mallado implica una cantidad enorme de nodos. Por otro lado se intenta minimizar la precisión en zonas donde no se requiere gran nitidez durante el procesamiento o el análisis de resultados.

lunes, 10 de octubre de 2011

Code Saturne: Realizando una simulación numérica del experimento

Ya con varias piezas dispuestas para la experiencia del control del cilindro, nos es necesario datos más cercanos a la realidad para probar los módulos por separado. Si bien se podría plantear el experimento y obtener imágenes del mismo, resulta más simple (y flexible) realizar una simulación numérica. A tal efecto, elegimos la librería de código abierto Code Saturne.


Dado que la instalación es un tanto tediosa y se lleva a cabo frecuentemente en el laboratorio, creamos una wiki para el laboratorio y tomamos nota de los pasos utilizando el archivo por lotes: install_saturne.py. Para más información, ver aquí.

La ejecución no deja de ser algo compleja:
  1. Se debe definir la experiencia con su simetría y parámetros físicos
  2. Se debe utilizar un programa de mallado para generar la cuadrícula para simular la física elegida. En este caso se utiliza el programa GID.
  3. Se debe correr el sistema de simulación Code Saturne en alguna de sus variantes
    1. Mediante el entorno gráfico.
    2. Realizando la configuración por archivos y corriendo el sistema por línea de consola. Esta opción es muy común para poder correr los trabajos de forma remota. Se agrega un instructivo a la flamante wiki del laboratorio. Para más información, ver aquí.

En el proceso nos topamos con un blog muy interesante de un estudiante de fluidodinámica del Reino Unido que vale la pena visitar:
http://code-saturne.blogspot.com/


sábado, 17 de septiembre de 2011

Probando el código C/CUDA (o C/C++) usando Matlab

Dado que comunicar Matlab con el código C/CUDA (o C/C++) no es la principal preocupación para la tesis, se aplica un poca investigación al tema y se implementa rápidamente un protocolo que permite comunicar entradas y salidas. En este caso, la comunicación consta de varias matrices de entrada y varias de salida. En todos los casos se trata de números flotantes.
Para que la ejecución del programa escrito en C sea transparente, se crea una función Matlab con el mismo nombre que el programa C, esta función será la encargada de grabar en disco las variables Matlab, llamar al ejecutable escrito en C, esperar que concluya exitosamente y leer las variables de salida desde el disco.
Las variables en disco se crean en una ubicación definida por el código Matlab (generalmente en una carpeta temporal) y se comunican al programa C mediante argumentos

Este esquema tiene un payload asociado a la lectura/escritura en disco de las variables que podría evitarse con algún método más directo...
A futuro, se recomienda utilizar la compilación MEX de Matlab:
http://www.mathworks.com/help/techdoc/matlab_external/f23224.html

jueves, 1 de septiembre de 2011

Librerías sobre Operaciones matemáticas en C/C++

A la hora de realizar los programas de prueba en C/C++, previo a codificarlos en C/CUDA fue necesario definir ciertas funciones de soporte matemáticas.

Como todo sistema de operaciones básicas comenzó con una clase Matrix<T> propia, con operaciones simples como multiplicar o sumar. A medida que el sistema creció, fue necesario implementar transposiciones y vistas de columnas o filas de la matriz. En ese punto, y buscando una implementación de inversión de matrices, resultaba obvia la necesidad de buscar un mejor soporte en librerías existentes.
A continuación, una colección de links relacionados:

Luego de evaluar todas las posibilidades, optamos por los dos primeros links: utilizar GSL y realizar una compilación custom para Windows.

jueves, 14 de julio de 2011

Simulando modelos conocidos

Matlab provee varias funciones que son de utilidad para simular modelos del tipo SISO, MIMO, etc. Una de las más útiles es sim que, dado un conjunto de datos de entrada y/o salida más un modelo ya definido, aplica el modelo a los datos y retorna la salida ulterior.
Veamos un ejemplo:

%genero un modelo según y(n) = u(n) - 2u(n-1)
A = [1] ; B = [1 -2];
modelo = idpoly(A, B);

%genero los datos para la simulación:
% ejemplos:
% iddata([], [1:100]') para salida nula y entrada del 1 al 100
% iddata([], idinput(100)) para salida nula y entrada aleatoria -1;1
data = iddata([],[1:10]');

%simulo
simulacion = sim(modelo, data);
plot(simulacion.y);

Bastante sencillo, si se cuenta con los datos de entrada e información sobre el modelo podemos calcular rápidamente la salida. 
Obviamente, se pueden aplicar relaciones mucho más complejas cambiando los coeficientes de A y B. Tanto es así que son esos vectores los que definen la transferencia G(s) que relaciona Y(s) y U(s) (salida con entrada). Entonces, el numerador evaluado en el operador de regresión q se convierte en A(q) mientras que el denominador hace lo propio en B(q). Si observamos con atención, podemos ver esa información en la variable modelo:
>> modelo
Discrete-time IDPOLY model: y(t) = B(q)u(t) + e(t)
B(q) = 1 - 2 q^-1                                                                                   
This model was not estimated from data.           
Sampling interval: 1 

miércoles, 15 de junio de 2011

Y dónde está cutil64.dll??

Muchos de los ejemplos que circulan por internet utilizan esta DLL. Tiene utilitarios generales para CUDA y viene dentro del "GPU Computing SDK examples". Para facilitar las cosas, es conveniente tomar esa librería (la versión correcta y para la plataforma correcta) de su carpeta en el SDK y colocarla dentro de la carpeta de instalación de CUDA. Lo mismo ocurre con los correspondientes archivos .lib.
Entonces, para un sistema 64 bits, copiamos:
  • <NVIDIA GPU Computing SDK Folder>\C\inc\cutil.h en <CUDA Folder>\include
  • <NVIDIA GPU Computing SDK Folder>\C\bin\win64\Release\cutil64.dll en <CUDA Folder>\bin64
  • <NVIDIA GPU Computing SDK Folder>\C\bin\win64\Debug\cutil64D.dll en <CUDA Folder>\bin64
  • <NVIDIA GPU Computing SDK Folder>\C\common\lib\cutil64.lib en <CUDA Folder>\lib64
  • <NVIDIA GPU Computing SDK Folder>\C\common\lib\cutil64D.lib en <CUDA Folder>\lib64
A modo anecdótico, dejo unos comentarios sobre la cutil.dll del release notes de una versión de CUDA:
      - Most samples link to a utility library called "cutil" whose source code
        is in "NVIDIA CUDA SDK\common". The release and emurelease versions of
        these samples link to cutil[32|64].lib and dynamically load
    cutil[32|64].dll. The debug and emudebug versions of these samples link
    to cutil[32D|64D].lib and dynamically load cutil[32D|64D].dll.
        To build the 32-bit and/or 64-bit, release and/or debug configurations
    of the cutil library, use the solution files located in
        "NVIDIA CUDA SDK\common". The output of the compilation goes to
        "NVIDIA CUDA SDK\common\lib":
         - cutil[32|64].lib and cutil[32D|64D].lib are the release and debug
       import libraries,
         - cutil[32|64].dll and cutil[32D|64D].dll are the release and debug
           dynamic-link libraries, which get also copied to
           "NVIDIA CUDA SDK\bin\win[32|64]\[release|emurelease]" and
           "NVIDIA CUDA SDK\bin\win[32|64]\[debug|emudebug]"
       respectively;

lunes, 13 de junio de 2011

Volviendo el tiempo atrás... CUDA 2.3

Lamentablemente, y aunque la sintaxis de CUDA 4 está muy buena, debemos volver a la versión 2.3 sobre Windows y compilar el código del Onera [1] con una resolución del Lucas-Kanade en GPU.

Lamentablemente, como suele suceder, aparecen nuevos problemas a salvar:
  • NVCC sin un compilador no funciona. Necesitamos el Visual Studio...
  • Visual Studio 2008 Express no tiene soporte para procesadores x64 con lo que hay que optar por una versión Trial del Professional.
  • Para compilar por línea de comandos nos quedamos con un ejemplo pequeño de archivo .cu y logramos compilarlo sólo luego de leer un post interesante [2] en el foro de nvidia: es necesario ejecutar un batch que setea el entorno del VS en modo amd64 y luego indicar algunos parámetros extras al NVCC.
  • Dentro del VS2008, NVCC sigue sin compilar. Es necesario actualizar el archivo nvcc.profile para que tenga un include extra del visual:
INCLUDES += "-I$(TOP)/include" "-I$(TOP)/include/cudart" "-IC:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/include" $(_SPACE_)

[1] Onera - FOLKI GPU
[2] Post compilación x64

viernes, 10 de junio de 2011

Eligiendo la cámara - Conclusiones

Ganador momentaneo: PixelFly

Conclusiones generales:
  1. La Pulnix es demasiado lenta. Además no ofrece ninguna ventaja significativa.
  2. La PixelFly tiene un buen tiempo de transmisión con binning activado. Según las pruebas de concepto se obtendrían 10 ms entre frame y frame con lo cual se puede usar esta cámara.
  3. La PixelFly tiene además modo de doble shutter que no fue analizado pero brinda más alternativas.
  4. La SpeedCam, aún siendo la de mejor calidad y velocidad no tuvo buenos resultados. La transferencia resulta muy lenta con lo que queda descartada por el momento.
  5. Es importante aclarar que el análisis se basa en la necesidad de hacer un algoritmo lo más veloz posible y poder realiar cálculos en el momento. En caso de no ser esto así se puede reconsiderar los resultados y optar por la SpeedCam en lugar de la PixelFly, por ejemplo. Esto podría ocurrir en caso que se necesite tiempo extra para realizar procesamiento, no requerir 1 frame cada 10 ms sino 2 frames espaciados en 10 ms para, más tarde, repetir la toma de fotos y el proceso.

sábado, 4 de junio de 2011

Tiempos - Usando la SpeedCam programáticamente

En este punto, la experiencia se convirtió en algo bastante pesado. La SpeedCam no tiene una API de acceso programática. Tampoco permite sacar fotogramas de forma unitaria, sólo se puede sacar una ráfaga que es almacenada en el buffer interno de la cámara y luego transmitido a la PC.
La cámara posee código hecho en Java del cual se pueden obtener los .jar para emplearlos en un código de prueba.
Consultamos entonces a los fabricantes: nos responden que la compañía cambió de manos. Consultamos a los nuevos responsables y obtenemos como información que tenemos tres caminos:
  • utilizar una API a nivel registro con la cual se accede a la cámara enviando códigos de operación
  • revisar los archivos minivis.jar y minivis.dll
  • comprar otra cámara o adquirir el nuevo software.
Obviamente nos quedamos con la segunda por "simplicidad" (si es que se puede decir que esto sea simple). Entonces, armamos un archivo .Java y tratamos de adivinar cómo se usa la API que contiene el .jar.
Luego de varios días de revisar los resultados del Java Decompiler. Obtenemos una sucesión de pasos que permite obtener las imágenes con resultados bastante malos:
  • Snapshots tomados: 200
  • fps entre Snapshots: 2500
  • Tiempo Total: 7800ms (sólo en la transferencia, ya que la toma de imágenes se debe hacer previa a la este paso)
  • Tiempo Promedio: 39ms (es decir, 25 fps)
Conclusión: muy mala velocidad para una cámara tan potente. Claramente, no está optimizada para utilizarla de forma programática en entornos real-time. Por el contrario, el buffer parece consumir mucho tiempo de la transferencia.
Pero entonces surge una duda ¿Y si los tiempos son lentos por estar utilizando java? Para tratar de falsear esta teoría, recurrimos a código C++ que intente levantar las DLLs directamente y no utilizar minivis.jar. Nuevamente tenemos que decompilar, en este caso decompilamos el archivo minivis.dll y hacemos un matcheo contra lo observado en minivis.jar. Igualmente, debemos notar que ambos archivos se comunican gracias a que se empleó JNI en C++. Lamentablemente esto simplemente complica la tarea de decompilación y reuso de la DLL original.
Con una sola prueba de concepto nos queda claro que ese no es el mejor camino:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention

Aquí el código Java:


   1:  class DummyConsumer implements CallbackConsumer {
   2:      public void modeChanged(int paramInt){
   3:      }
   4:      public void statusChanged(int paramInt) {
   5:      }
   6:      public void shutterChanged(boolean paramBoolean, short paramShort) {
   7:      }
   8:  }
   9:   
  10:  public class Application {
  11:      public static void main(String[] arguments) {
  12:          int numberOfImages = Integer.parseInt(arguments[0]);
  13:          
  14:          MinivisFactory factory = MinivisFactory.getInstance();
  15:          try {
  16:              factory.discover();
  17:              TypedNetAddress[] addresses = factory.getKnownDevices();
  18:              String mac = addresses.length > 0 ? addresses[0].getEntryName() : "00-50-C2-1D-7E-AB";
  19:              DummyConsumer callbackConsumer = new DummyConsumer();
  20:              Proxy camera = factory.getCamera(mac, callbackConsumer);
  21:              try {
  22:                  camera.connect();
  23:                  try {
  24:                      takeSnapshots(camera, numberOfImages, debug);
  25:                  }            
  26:                  finally {
  27:                      camera.disconnect();
  28:                  }
  29:              }
  30:              finally {
  31:                  camera.release();
  32:              }
  33:          }
  34:          catch(Exception e) {
  35:              e.printStackTrace();
  36:          }
  37:      }
  38:      private static void takeSnapshots(Proxy camera, int numberOfImages, boolean debug) throws IOException, MDriverException, Exception {
  39:          camera.setMode(4); //0 LIVE 1 RECORD 2 TRIGGERED 3 DRAM 4 LOWLIGHT
  40:          camera.trigger();
  41:          
  42:          //missing code here: wait for trigger to finish
  43:          
  44:          MFrameInfo frameInfo = new MFrameInfo();
  45:          for(int i = 0; i < numberOfImages; ++i) {
  46:              byte[] frameBytes = camera.getFrame(-1, -1  , frameInfo);
  47:          }
  48:      }
  49:  }

Aquí el código C++:


   1:   
   2:  #include "stdafx.h"
   3:  #include <iostream>
   4:   
   5:   
   6:  int _tmain(int argc, _TCHAR* argv[])
   7:  {
   8:      HMODULE hMod = LoadLibrary("minivis.dll");
   9:      typedef long (*MinivisFactory_Create)();
  10:      typedef long (*GetCamera)(long, char*, void*);
  11:      MinivisFactory_Create n_MinivisFactory_Create = (MinivisFactory_Create)GetProcAdress("_Java_com_artho_visart_plugins_minivis_internal_jni_MinivisFactory_n_1MinivisFactory_1Create@8");
  12:      GetCamera n_GetCamera = (GetCamera)GetProcAdress("_Java_com_artho_visart_plugins_minivis_internal_jni_MinivisFactory_n_1GetCamera@24");
  13:      
  14:      
  15:      long adapterPtr = n_MinivisFactory_Create();
  16:      void* consumer = NULL;
  17:      long pointer = n_GetCamera(adapterPtr, "00-50-C2-1D-7E-AB", consumer);
  18:      
  19:      std::cout << "Camera pointer:" << pointer << std::endl;
  20:      std::cout << "Press any key to continue..." << std::endl;
  21:      char character;
  22:      std::cin >> character;
  23:      return 0;
  24:  }

lunes, 30 de mayo de 2011

Tiempos - Usando la PixelFly programáticamente

Similar al caso de la Pulnix, se escribe código usando la API provista para PixelFly y se miden los tiempos aproximados. En este caso se cuenta con dos APIs a utilizar: la original de la cámara (para VC++) y un SDK unificado para las PixelFly y las Sensicam llamado Uniform SDK. Se opta por el último por tener una interfaz más clara y no requerir elementos de WinApi para funcionar; según la documentación esta interfaz es más lenta que la original, pero igualmente sirve para computar órdenes de magnitud.

Resultados:
  • Snapshots tomados: 200
  • fps entre Snapshots: 50
  • Tiempo Total: 4000ms
  • Tiempo Promedio: 20 ms (es decir, 50 fps)
Misma conclusión que la obtenida con la Pulnix: transferencia despreciable. Igualmente seguimos teniendo el doble del tiempo previsto para la toma de fotos.
Afortunadamente, la PixelFly presenta una opción de binning que puede reducir los tiempos empleados. El binning consiste en realizar algún procesamiento sobre un conjunto de píxeles y generar un único píxel que contiene información de los anteriores. En el común de los casos la operación consiste en promediar los valores.
Los nuevos resultados:

  • Snapshots tomados: 200
  • fps entre Snapshots: ?
  • Tiempo Total: 2000ms
  • Tiempo Promedio: 10 ms (es decir, 100 fps)
 Conclusión: ya tenemos el tiempo que queríamos !!
Al parecer el binning permite incluso que la cámara tome fotografías en una frecuencia más alta. Queda pendiente evaluar la capacidad de double shutter.
    Aquí el código:


       1:  #define GAIN 1
       2:  #define DELAY 0 //ms
       3:  #define EXPOSURE_TIME 5 //ms
       4:  #define ROIX 2 //from 1 to 20
       5:  #define ROIY 2 //from 1 to 20
       6:   
       7:  int main(int argc, char* argv[]) {
       8:      int totalSnapshots;
       9:      std::cout << "Qty of snapshots to take: ";
      10:      std::cin >> totalSnapshots;
      11:      
      12:      int camId;
      13:      CAMINFO camData[8];
      14:   
      15:      int boardNumber = 0;
      16:      int error;
      17:      if (error = SELECT_CAMERA("pixelfly", boardNumber, &camId, camData))
      18:          showErrorAndClose(error);
      19:      else {
      20:          if (error = SETUP_CAMERA(camId, camData, 0, 0, 0, 1, ROIX, 1, ROIY, 1, 1, GAIN, DELAY, EXPOSURE_TIME))
      21:              showErrorAndClose(error);
      22:          else {    
      23:              time_t beginTime, endTime;
      24:              time(&beginTime);
      25:      
      26:              int snapshotNumber = 0;
      27:              while (! error && snapshotNumber < totalSnapshots) {
      28:                  if (error = SNAP(camId, camData, 0))
      29:                      showErrorAndClose(error);
      30:                  else {
      31:                      if (error = GETIMAGE(camId, camData, 2000))
      32:                          showErrorAndClose(error);
      33:                      else 
      34:                          // image in memory at this point
      35:                  }
      36:                  snapshotNumber++;
      37:              }
      38:              time(&endTime);
      39:              std::cout << std::endl << "Tiempo Promedio por imagen: " << 1000.0 * (double)difftime(endTime, beginTime) / (float)totalSnapshots << "ms." << std::endl;
      40:          }
      41:          CLOSE_CAMERA(camId, camData);
      42:      }
      43:      return 0;
      44:  }

    miércoles, 25 de mayo de 2011

    Tiempos - Usando la Pulnix programáticamente

    Código feo... necesitamos saber si el tiempo de transferencia de la Pulnix con la placa adquisidora es suficiente para alcanzar los 100 fps.
    Usamos la API de Imagenation (muy vieja pero efectiva). Sacamos N snapshots y medimos el tiempo entre cada una de ellas usando time().
    Resultados:
    • Snapshots tomados: 120
    • fps entre Snapshots: 30
    • Tiempo Total: 4000ms
    • Tiempo Promedio: 33.3ms (es decir, 30 fps)

    Conclusión: el tiempo de transferencia es despreciable o bien se compenza con el tiempo de toma de la próxima foto.

    Aquí un snippet del código:


       1:  int main(int argc, char* arg[])
       2:  {
       3:      PXD pxd;
       4:      FRAMELIB frameLib;
       5:      long hFG=0;
       6:      char arcFileName[20];
       7:      static int num=0;
       8:      
       9:      int NO_FRAMES;
      10:      std::cout << "Ingrese el Nro de imágenes a capturar (30 fps)"; std::cin >> NO_FRAMES;
      11:   
      12:      imagenation_OpenLibrary("PXD_32.DLL", &pxd, sizeof(PXD));
      13:      imagenation_OpenLibrary ("frame_32.dll", &frameLib, sizeof(FRAMELIB));
      14:   
      15:      
      16:      //"tm-9701 progressive free-run.cam";
      17:      char configFile[] = {"C:\\PXD\\bin\\default.cam"};
      18:      hFG= pxd.AllocateFG (-1);
      19:   
      20:      CAMERA_TYPE *configInMem = pxd.LoadConfig(configFile);
      21:      pxd.SetCameraConfig(hFG,configInMem);
      22:      pxd.FreeConfig(configInMem);
      23:      time_t beginTime, endTime;
      24:      printf("Inicio de Captura %d imagenes \n\n",NO_FRAMES);
      25:      time(&beginTime);
      26:      for(num= 0; num {
      27:          time(&captureTime);
      28:          FRAME* pFRAME = pFRAME = pxd.AllocateBufferList (pxd.GetWidth(hFG), pxd.GetHeight(hFG), pxd.GetPixelType(hFG), 1 /*solo un frame*/);
      29:          pxd.Grab (hFG, pFRAME,IMMEDIATE);
      30:          sprintf(arcFileName,"30fps%.3d.bmp", num);
      31:          frameLib.ExtractPlane(pFRAME,1);
      32:          //frameLib.WriteBMP ( frameLib.ExtractPlane(pFRAME,1), arcFileName,1);
      33:          frameLib.FreeFrame (pFRAME);
      34:      }
      35:   
      36:      time(&endTime);
      37:      std::cout << std::endl << "Tiempo Promedio por imagen: " << 1000.0 * (double)difftime(endTime, beginTime) / (float)NO_FRAMES << "ms." << std::endl;
      38:      pxd.FreeFG (hFG);
      39:      return 0;
      40:  }
      41:      
      42:      

    miércoles, 4 de mayo de 2011

    Eligiendo la cámara

    Como ya se comentó, es necesario tomar imágenes del sistema bajo estudio para realizar ciertos chequeos y de esa forma alimentar al lazo cerrado. Por lo tanto, el siguente paso será realizar mediciones mediante las distíntas cámaras que se poseen en el laboratorio. Como siempre, realizamos una tabla comparativa:

    Nombre Tipo Resolución Max FPS Cropping / ROI Buffer / Tipo de Transferencia Url
    PXD Frame Grabber Family - Cyber Optics Placa Adq. 32Kx32K 160 (512x512 con 32bits) Si Interfaz  propietaria Link
    PixelFly qe - Cooke Coroporation Cámara 640x480 50 Si Sin buffer. Transf. directa por RJ45 en placa Adq. propietaria Link
    SpeedCam Mini Vis e2 - Weinberger Cámara 512x512 2500 Si2GB buffer. Luego Transf. por placa Ethernet 1 Gbit Link
    TM-9701 - Pulnix Cámara 768x484 30 No Sin buffer. Transf. directa por placa Adq. PXD Link

    Si bien la SpeedCam Minivis es la mejor cámara de las tres disponibles, la mejor opción siempre depende de las características que se necesiten.

    En nuestro caso particular son:

    1. Debe ser programable.
    2. Se necesitan frames con una Dif. de tiempo menor a 10ms. Es decir, 100 fps.
    3. Si bien la Dif. entre frames debe ser menor a 10ms, se puede aceptar un tiempo de transferencia un tanto mayor pero que permita procesar frames de a pares, retornar respuesta al sistema y volver a pedir un par de frames.
    4. Tanto la luminosidad como la resolución no son factores importantes.
    Lamentablemente, los puntos 1 a 3 deben ser confirmados empíricamente con lo cual se demora la elección hasta tener información en el campo práctico sobre todas las cámaras.

    martes, 26 de abril de 2011

    Placa más computadora instaladas y funcionando

    Ya está la placa GTX570 funcionando en un Ubuntu. Posiblemente sea necesario realizar la instalación para Windows dado que muchos de los sensores y actuadores se comunican usando drivers propietarios de Windows.

    Se instaló CUDA Tools y Toolkit versión 4.0 (release candidate 2, mejor arrancar con el soft de vanguardia dado que lo tienen casi en una versión productiva) junto con el GPU SDK examples de Nvidia (ver).


    Linux
    Luego de instalar todos los paquetes necesarios (libgl1-mesa-dev, libgl1-mesa-dri, libglu-mesa-dev, freeglut3-dev, libxmu-dev, libxi-dev, etc) y de instalar/reinstalar los drivers y toolkits de Nvidia varias veces llegamos a la primera compilación. Obviamente se trata de ejemplos pre-armados, dentro del SDK. Compilamos y corremos algunos tests como deviceQuery y bandwidthTest:

    cd ~/workspace/NVIDIA_GPU_Computing_SDK
    make
    cd C/bin/linux/release
    ./deviceQuery

    ./deviceQuery Starting...

     CUDA Device Query (Runtime API) version (CUDART static linking)

    There is 1 device supporting CUDA

    Device 0: "GeForce GTX 570"
      CUDA Driver Version / Runtime Version          4.0 / 4.0
      CUDA Capability Major/Minor version number:    2.0
      Total amount of global memory:                 1279 MBytes (1341325312 bytes)
      (15) Multiprocessors x (32) CUDA Cores/MP:     480 CUDA Cores
      GPU Clock Speed:                               1.57 GHz
      Memory Clock rate:                             2100.00 Mhz
      Memory Bus Width:                              320-bit
      L2 Cache Size:                                 655360 bytes
      Max Texture Dimension Size (x,y,z)             1D=(65536), 2D=(65536,65535), 3D=(2048,2048,2048)
      Max Layered Texture Size (dim) x layers        1D=(16384) x 2048, 2D=(16384,16384) x 2048
      Total amount of constant memory:               65536 bytes
      Total amount of shared memory per block:       49152 bytes
      Total number of registers available per block: 32768
      Warp size:                                     32
      Maximum number of threads per block:           1024
      Maximum sizes of each dimension of a block:    1024 x 1024 x 64
      Maximum sizes of each dimension of a grid:     65535 x 65535 x 65535
      Maximum memory pitch:                          2147483647 bytes
      Texture alignment:                             512 bytes
      Concurrent copy and execution:                 Yes with 1 copy engine(s)
      Run time limit on kernels:                     No
      Integrated GPU sharing Host Memory:            No
      Support host page-locked memory mapping:       Yes
      Concurrent kernel execution:                   Yes
      Alignment requirement for Surfaces:            Yes
      Device has ECC support enabled:                No
      Device is using TCC driver mode:               No
      Device supports Unified Addressing (UVA):      Yes
      Device PCI Bus ID / PCI location ID:           3 / 0
      Compute Mode:
         < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

    deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 4.0, CUDA Runtime Version = 4.0, NumDevs = 1, Device = GeForce GTX 570
    [./deviceQuery] test results...
    PASSED

    ./bandwidthTest


    ./bandwidthTest Starting...

    Running on...

     Device 0: GeForce GTX 570
     Quick Mode

     Host to Device Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes) Bandwidth(MB/s)
       33554432 2961.9

     Device to Host Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes) Bandwidth(MB/s)
       33554432 2753.8

     Device to Device Bandwidth, 1 Device(s)
       Transfer Size (Bytes) Bandwidth(MB/s)
       33554432 130016.2

    [./bandwidthTest] test results...
    PASSED

    Win7 
    La instalación demoró muchísimo menos tiempo (menos de dos horas totales) comparada con la instalación en Linux (unas 12 horas de lucha).
    El deviceQuery entrega CASI los mismos resultados. Hay diferencia en


      Run time limit on kernels:                     Yes
      Device supports Unified Addressing (UVA):      No


    que luego investigaremos.

    Por otro lado, el bandwidthTest entrega:

     Host to Device Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes)        Bandwidth(MB/s)
       33554432                     2521.2
     Device to Host Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes)        Bandwidth(MB/s)
       33554432                     2551.9
     Device to Device Bandwidth, 1 Device(s)
       Transfer Size (Bytes)        Bandwidth(MB/s)
       33554432                     130377.2

    que claramente ofrece menos performance que en la versión Linux.


    miércoles, 2 de marzo de 2011

    Comparador de performance online para CPU, GPU, etc.

    No sé si es un hallazgo, pero no dejó de impresionarme tener tanta información para poder decidir sobre uno u otro producto:

    http://www.anandtech.com

    martes, 1 de marzo de 2011

    ¿Priorizando relación precio-prestaciones? No siempre.

    Estuvimos analizando las tablas comparativas de las distintas placas. El análisis resultante es el siguiente:

    Serie Tesla: precios muy elevados. Gama muy alta de placas sin siquiera salida de video (son sólo para procesamiento). Tienen soporte para escalar horizontalmente (más placas) por driver. Cuenta con buena performance para doble precisión (DP) -de aprox. 1/2 respecto de simple precisión- y soporte ECC para la memoria (un problema bastante frecuente en GPGPU es la alta tasa de error en memorias) en varios modelos.

    Serie Quadro: uso compartido entre cálculo y gráficas de alta calidad. Al igual que la Tesla, tienen soporte ECC para algunos modelos (aunque el costo es mucho más elevado) y buen manejo de DP.

    Serie Quadro FX: muy mala en comparación con la anterior. Orientada a video juegos por completo. Los costos no disminuyen en todos los casos.

    Serie GTX500: muy buena relación precio-prestaciones. Son modelos nuevos bastante competitivos con buenas velocidades, cantidad de memoria, ancho de banda, cores CUDA, etc. Lamentablemente no tienen soporte DP de calidad (1/8 respecto de simple precisión. Ver Ref.) ni ECC en ningún caso. La 580 está algo sobrevaluada por ser la más nueva de las placas. La 560 es la más barata y aún así no reduce mucho las características.

    Serie GTX400: al parecer va a quedar obsoleta. Prestaciones similares a la serie 500 con costos más elevados. Quejas de usuarios por performance, ruidos molestos ¿? :), etc.

    Serie GTS450: muy buena opción económica. El precio es muy bueno para las características generales. Lamentablemente estamos buscando un nivel más alto de paralelismo.


    Conclusiones finales:
    Tesla descartada por excesivo. Quadro se vuelve muy caro por importación y las tarjetas con ECC de por sí duplican el costo. En cualquiera de los casos no se prevé hacer uso intensivo de DP de cálculo con lo cual se opta por series GTX500 o GTX400. 
    Dado que la serie 500 posee mejores perspectivas a futuro, nos quedamos con ella. Se descarta la placa 580 por ser la última de la serie y tener un costo excesivo. Finalmente nos quedamos con la GTX570 sobre la 560 por tener mejores prestaciones. Aunque se reconoce que la 560 posee una mejor relación precio-características se opta por el modelo siguiente para tener una PC más competitiva y amortizar durante un lapso más grande (meses) el tiempo invertido en la compra, instalación, puesta a punto, capacitación y pruebas.


    miércoles, 23 de febrero de 2011

    Buscando la placa

    Primera lista de opciones:
    • Propuestas por en "GPUs, a new tool of acceleration in CFD: Efficiency and reliability on Smoothed Particle Hydrodynamics methods": GTX 260, TESLA M1060, GTX 285, GTX 480
    • Propuestas por contactos de España: GTX580, GTX570
    • Propuestas por amigos de Argentina: GTX480, QUADRO6000, GTX560
    • Propuesta por contactos de Exactas: GT450
    Resumen de las características y disponibilidades:



      Procesadores
      Performance
      Memoria
      Modelo
      Cores
      Clock [Mhz]
      (c/u)
      Single [Gflops]
      Double [Gflops]
      Tam. [GB]
      Clock [Ghz]
      Ancho de Banda
      [GB/sec]
      Tipo
      Precio estimado [us$]
      Link
      TeslaM20504481.15103051531.55148GDDR5 ECC>us$ 2400 (amazon)Nvidia
      TeslaM10602401.39337840.8102GDDR3>us$ 1600 (amazon)Nvidia
      Quadro50003520.5137183592.50.75120GDDR5 ECCus$ 1800 (amazon)Nvidia
      Wiki
      Quadro40002560.47548624320.789GDDR5us$ 810 (amazon)Nvidia
      Wiki
      GTX5805121.515811.52192GDDR5us$ 790Nvidia
      Wiki
      GTX4804801.413441.51.8177GDDR5us$ 650Nvidia
      Wiki
      QuadroFX1800640.5502640.7680.838.4GDDR3us$ 600Nvidia
      Wiki
      GTX5704801.4614051.281.9152GDDR5us$ 560Nvidia
      Wiki
      GTX5603841.6412631?128GDDR5us$ 410Nvidia
      Wiki
      GTS4501921.560111.857.7GDDR5us$ 220Nvidia
      Wiki



      Los números en rojo para los GFlops resultan muy extraños en comparación con las placas de gama alta. Dado no hay soporte para doble precisión salvo en gama alta, es de esperar que para comparar con gama alta haya que tomar ambos GFlops (SP y DP) y aplicar un factor de escala.
      Las placas Quadro FX580 y el Quadro FX4500 (disponibles en Argentina) se descartan por tener pocas prestaciones.







      Cambio de planes: sin sensores, con imágenes

      Al parecer la solución con sensores podría traer muchas complicaciones:

      • son necesarios en cantidad con lo cual la instalación demanda tiempo
      • son muy intrusivos, el fluido puede variar del comportamiento original
      • el ruido que conllevan puede ser muy elevado en momentos donde los actuadores de plasma estén funcionando
      Para que la experiencia sea realizable, se opta por tomar y procesar imágenes del fluido descartando otro tipo de sensores. Del procesamiento se obtendrá la información requerida, como ser el campo de velocidades, y se realizará el feedback que necesita el controlador.
      Pero el empleo de grandes imágenes como origen de datos requiere de mayor tiempo de CPU aplicado a su procesamiento. Dado que se plantea realizar un sistema real-time no se puede dejar de tomar este punto como un futuro problema. La posible solución recae en paralelizar el procesamiento de las imágenes, dividiéndolas y obteniendo información rápida para actuar en el lazo de control. Para poder aplicar el nivel de procesamiento indicado será necesario utilizar programación de GPU. Ver http://en.wikipedia.org/wiki/GPGPU.

      Si bien esto no es parte de la tesis original dá un vuelco completo al posible título. Además, es necesario elegir el hardware adecuado e instalarlo. Esto posiblemente lleve algunas semanas.

      El problema inicial

      Lugar: Laboratorio de Fluidodinámica - Facultad de Ingeniería - UBA

      Participantes:
      • Guillermo Artana
      • Ada Camilleri
      • Thomas Duriez
      • Resto del personal del laboratorio.

      Problema: realizar un sistema que controle de forma automática el fluido bajo cierta experiencia pautada. Se plantea el uso de un experimento prototípoco con un cilindro en el tunel de viento con el objetivo de reducir las turbulencias en la estela que se forma.

      Solución: los sistemas de lazo cerrado real-time sobre fluidos no suelen implementarse con buenos resultados, básicamente por sus problemas de performance. Se desea intentar este tipo de solución con la ayuda de una placa gráfica que permita el speed-up suficiente para montar la experiencia con buenos resultados.

      El laboratorio cuenta con una experiencia de simulación realizada en colaboración por Ada Camilleri y Lionel Mathelin sobre un sistema de control de fluido basado en la identificación del sistema con un modelo ARX. La idea es llevar esta simulación a la implementación real de la experiencia.

      martes, 1 de febrero de 2011

      Los primeros pasos

      Pasos para hacer una tesis de Ingeniería en Informática:

      • juntar coraje
      • buscar un tema. Admite diversas variantes: teórica, de aplicación ingenieril o de aplicación científica. Me quedé con la última.
        • tomar nota de los posibles laboratorios a visitar. Lleva un tiempo leer las reseñas de todos: Laboratorios en FIUBA
        • recorrer los laboratorios que te resulten más interesantes preguntando por las experiencias y líneas de investigación que tienen.
        • tomar nota de todo.
        • pedir algunas referencias o papers para investigar las mejores alternativas.
        • optar por una que resulte interesante, la gente del laboratorio esté disponible y parezca alcanzable.
      • conseguir un tutor
      • arrancar
      • armar un blog