Unitarium4C v0.2

Acabo de liberar la versión 0.2 de Unitarium4C , una librería destinada a facilitar la creación de suites de testeo unitario. Considero que esta versión ya puede ser calificada como una versión beta, a diferencia de la primera, que era una clarísima versión alpha (le faltaba de todo y era, por decirlo suavemente, poco más que un buñuelo).

Sin más, dejo el Changelog :) :

  • Añadida creación automatizada de librerías estáticas y compartidas.
  • Añadida orden en el Makefile para instalar las librerías y poder usarlas fácilmente en cualquier proyecto.
  • Añadidos nuevos tipos y nuevos asserts (float, char*).
  • Limpiado el código (y eliminados algunos errores oscuros y con poca probabilidad de ver la luz del día)
  • Facilitada la creación de extensiones por parte de otros desarrolladores
  • El código ahora está preparado para (en la próxima versión) poder exportar XML y/o JSON con los datos de los tests.

Saludos D

Unitarium4C v0.1

Recientemente he creado una pequeña librería para construir suites de testeo unitario usando el lenguaje C , por el momento soporta algunos asserts sencillos (para valores booleanos, y comparaciones entre enteros y números de punto flotante), y supongo que en breve remodelaré ligeramente el código para poder ampliar mucho más fácilmente el conjunto de datos sobre los que puede operar la librería.

Los principales problemas que han surgido han estado ligados a la necesidad de un modelo de programación próximo a la orientación a objetos, mientras que en C no existe nada de eso (salvo si se usa Glib con sus GObject, el problema es que eso es demasiado complicado para lo que yo pretendía hacer).

¿Cual ha sido la solución? Crear estructuras encapsuladoras para cada dato atómico, con sus respectivas funciones destructoras, punteros a punta pala y toda la parafernalia que ya os podéis estar imaginando. Lo que obviamente me ha llevado a perder horas buscando dónde había fugas de memoria... y peor aún, violaciones de segmento. El caso es que ahora la librería parece ser estable y segun valgrind no hay ninguna fuga :D .

Dejo un enlace a la librería que he escrito: http://gitorious.org/unitarium4c .

Saludos.

Wt : C++ Webtoolkit, un gran descubrimiento

Hoy he descubierto a través del planet de gnome ( planet.gnome.org -> http://jaap.haitsma.org/ -> Make AJAX Web Applications with C++ ) las librerías Wt (Webtoolkit) que permiten crear aplicaciones web AJAX con el lenguaje de programación C++. Me ha resultado muy interesante porque la velocidad de ejecución de C++ es mucho mayor que la de la mayoría de lenguajes interpretados, ya sea con simples intérpretes, con bytecodes o incluso con JITs, hay que añadir que el consumo de memoria también acostumbra a ser inferior. Otras ventajas podrían ser la facildad para usar casi cualquier librería imaginable

Por otro lado tenemos que considerar los inconvenientes: el manejo de memoria con sus punteros, la asignación y la liberación de memoria puede resultar un engorro. Y no solo eso, la sintaxis de C++ es mucho más compleja de lo que puede ser la de Python, Ruby, C#, PHP u otros lenguajes más modernos, hay que tener verdadera paciencia con él.

Voy a ver que tal es trabajar con ella, ya os diré algo :) , pero parece que promete.

Referencias

Haciendo extensiones para Python

El otro día me planteé la cuestión de cómo crear un módulo para Python que fuera más eficiente que el propio Python, es decir, sin escribirlo en Python. Me puse a mirar la documentación oficial y lo encontré fácilmente, la verdad es que Python está muy bien documentado (cambiaría algunas cosillas, pero más por gustos personales que por que crea que están mal). La principal forma de crear módulos eficientes para Python es hacerlos en C o C++, y ésta es la forma que seguí yo. Antes de continuar, os preguntaréis... ¿Por qué querías hacer un módulo eficiente para Python? Pues... ni idea, jeje, por experimentar, y por ver si con mis conocimientos recién adquiridos (sí, realmente recién adquiridos) de métodos numéricos soy capaz de hacer una buena librería de cálculo científico (en particular para aplicar técnicas de interpolación) para Python. Sólo he visto la PyGMP, pero es posible que sea demasiado general, y no incluye algoritmos de interpolación (que es, en definitiva, lo que yo quiero hacer).

Por ahora sólo he incluido dos algoritmos muy sencillos, el de Neville para evaluar polinomios en un punto (de momento sólo real), y el método de las diferencias divididas de Newton, que nos da las diferencias divididas con las que podemos construir un polinomio interpolatorio. De momento he hecho sólo éstos dos porque he estado haciendo pruebas, y entre ellas han habido pruebas de rendimiento... la verdad es que me quedé pasmado al ver la diferencia entre C y Python (sí, ya sabía que Python era más lento, pero no tanto). El algoritmo de Neville llega a ser un 862% más rápido escrito en C que en Python (tarda sólo un 11.5% de lo que tardaría si estuviera escrito en Python), además, aunque no lo he comprobado, creo que a medida que aumentamos los puntos de soporte, el margen de tiempo aumenta todavía más (supongo que habrá un límite).

Ahora pasaré a explicar brevemente como lo he hecho, aunque para el que quiera profundizar, tendría que leer la documentación de Python directamente, esto debería ser sólo un ejemplo. Lo primero que debemos hacer es crear el código que queremos transformar en un módulo python (ya desde el principio tenemos que empezar utilizando los tipos de la API de Python):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#include <Python.h>
 
static PyObject *interpolation_iaval(PyObject *self, PyObject *args)
{
    double x;
    PyObject *a, *fa;
    PyObject *tmp;
    
    unsigned n, i, j;
    
    double *da, *dfa;
    
    if (!PyArg_ParseTuple(args, "dOO", &x, &a, &fa))
        return PyExc_AttributeError;
    if(!(PyList_Check(a) || PyList_Check(fa)))
        return PyExc_AttributeError;
    n = PyList_Size(a);
    if(n!=PyList_Size(fa))
        return PyExc_IndexError;
    
    da  = (double *)malloc(n*sizeof(double));
    dfa = (double *)malloc(n*sizeof(double));
    
    if(da==NULL || dfa==NULL)
    {
        if(da)  free(da);
        if(dfa) free(dfa);
        return PyErr_NoMemory();
    }
    
    for(i=0; i<n; i++)
    {
        tmp = PyList_GetItem(a, i);
        if(!PyFloat_Check(tmp))
        {
            free(da);
            free(dfa);
            return PyExc_AttributeError;
        }
        da[i] = PyFloat_AsDouble(tmp);
        
        tmp = PyList_GetItem(fa, i);
        if(!PyFloat_Check(tmp))
        {
            free(da);
            free(dfa);
            return PyExc_AttributeError;
        }
        dfa[i] = PyFloat_AsDouble(tmp);
    }
    
    n--;
    for(i=1; i<=n; i++)
        for(j=n; j>=i; j--)
            dfa[j] = ((x-da[j-i])*dfa[j]-(x-da[j])*dfa[j-1])/(da[j]-da[j-i]);
    
    x = dfa[n];
    
    free(da);
    free(dfa);
    
    return Py_BuildValue("d", x);
}
 
static PyObject *interpolation_divdif(PyObject *self, PyObject *args)
{
    PyObject *a, *fa;
    PyObject *tmp;
    
    unsigned n, i, j;
    
    double *da, *dfa;
    
    if (!PyArg_ParseTuple(args, "OO", &a, &fa))
        return PyExc_AttributeError;
    if(!(PyList_Check(a) || PyList_Check(fa)))
        return PyExc_AttributeError;
    n = PyList_Size(a);
    if(n!=PyList_Size(fa))
        return PyExc_IndexError;
    
    da  = (double *)malloc(n*sizeof(double));
    dfa = (double *)malloc(n*sizeof(double));
    
    if(da==NULL || dfa==NULL)
    {
        if(da)  free(da);
        if(dfa) free(dfa);
        return PyErr_NoMemory();
    }
    
    for(i=0; i<n; i++)
    {
        tmp = PyList_GetItem(a, i);
        if(!PyFloat_Check(tmp))
        {
            free(da);
            free(dfa);
            return PyExc_AttributeError;
        }
        da[i] = PyFloat_AsDouble(tmp);
        
        tmp = PyList_GetItem(fa, i);
        if(!PyFloat_Check(tmp))
        {
            free(da);
            free(dfa);
            return PyExc_AttributeError;
        }
        dfa[i] = PyFloat_AsDouble(tmp);
    }
    
    n--;
    for(i=1; i<=n; i++)
        for(j=n; j>=i; j--)
            dfa[j] = (dfa[j]-dfa[j-1])/(da[j]-da[j-i]);
    n++;
    tmp = PyList_New((Py_ssize_t)(n));
    for(i=0; i<n; i++)
        PyList_SetItem(tmp, (Py_ssize_t)i, PyFloat_FromDouble(dfa[i]));
    
    return tmp;
}
 
 
static PyObject *interpolation_hermite(PyObject *self, PyObject *args)
{
    PyObject *pa, *pfa;
    PyObject *t1, *t2;
    
    unsigned n, m, i, j, k, s;
    
    double   *a, **dfa, *dd, t;
    unsigned *nf;
    
    /* Codi de preparació pel còmput i de comprovacions */
    // printf("Inici de les preparacions...\n");
    if (!PyArg_ParseTuple(args, "OO", &pa, &pfa))
        return PyExc_AttributeError;
    if(!(PyList_Check(pa) || PyList_Check(pfa)))
        return PyExc_AttributeError;
    n = PyList_Size(pa);
    if(n!=PyList_Size(pfa))
        return PyExc_IndexError;
    
    nf  = (unsigned *)malloc(n*sizeof(unsigned));
    dfa = (double  **)malloc(n*sizeof(double*));
    
    if(dfa==NULL || nf ==NULL)
    {
        if(dfa) free(dfa);
        if(nf)  free(nf);
        return PyErr_NoMemory();
    }
    
    m = 0;
    for(i=0; i<n; i++)
    {
        t1 = PyList_GetItem(pfa, i);
        if(!PyList_Check(t1))
        {
            free(dfa);
            free(nf);
            
            return PyExc_AttributeError;
        }
        
        nf[i]  = (unsigned)PyList_Size(t1);
        m += nf[i];
        dfa[i] = (double *)malloc(sizeof(double)*nf[i]);
        if(dfa[i]==NULL)
        {
            for(k=0; k<=i; k++)
                free(dfa[k]);
            free(dfa);
            free(nf);
            
            return PyErr_NoMemory();
        }
        for(j=0; j<nf[i]; j++)
        {
            t2 = PyList_GetItem(t1, j);
            if(!PyFloat_Check(t2))
            {
                for(k=0; k<=i; k++)
                    free(dfa[k]);
                free(dfa);
                free(nf);
                return PyExc_AttributeError;
            }
            dfa[i][j] = PyFloat_AsDouble(t2);
        }
    }
    
    dd = (double *)malloc(sizeof(double)*m);
    a  = (double *)malloc(sizeof(double)*m);
    
    if(a == NULL || dd == NULL)
    {
        if(a)  free(a);
        if(dd) free(dd);
        for(i=0; i<n; i++)
            free(dfa[i]);
        free(dfa);
        free(nf);
        
        return PyErr_NoMemory();
    }
    
    for(k=0, i=0; i<n; i++)
    {
        t2 = PyList_GetItem(pa, i);
        if(!PyFloat_Check(t2))
        {
            if(a)  free(a);
            if(dd) free(dd);
            for(i=0; i<n; i++)
                free(dfa[i]);
            free(dfa);
            free(nf);
            
            return PyExc_AttributeError;
        }
        t = PyFloat_AsDouble(t2);
        
        for(j=0; j<nf[i]; j++, k++)
        {    
            a[k]  = t;
            dd[k] = dfa[i][0];
        }
    }
    /* Final de les comprovacions i de la preparació del còmput */
    // printf("Final de les comprovacions...\n");
    
    // printf("Inici del còmput...\n");
    /* Inici del còmput */
    m--;
    for(i=1; i<=m; i++)
        for(j=m; j>=i; j--)
        {
            if(a[j]!=a[j-i])
                dd[j] = (dd[j]-dd[j-1])/(a[j]-a[j-i]);
            else
            {
                t = a[0];
                for(s=k=0; k<n; k++)
                {
                    if(a[k]==a[j])
                        break;
                    if(a[k]!=t)
                    {
                        t = a[k];
                        s++;
                    }
                }
                dd[j] = dfa[s][i]/((double)fact(i));
            }
        }
    // printf("Final del comput...\n");
    
    // printf("Conversio de tipus...\n");
    m++;
    t1 = PyList_New((Py_ssize_t)(m));
    for(i=0; i<m; i++)
        PyList_SetItem(t1, (Py_ssize_t)i, PyFloat_FromDouble(dd[i]));
    // printf("Conversio realitzada...\n");
    
    // printf("Alliberant memòria...\n");
    free(nf);
    free(dd);
    for(i=0; i<n; i++)
        free(dfa[i]);
    free(dfa);
    // printf("Memoria alliberada...\n");
 
    return t1;
}
 
static PyMethodDef interpolationmethods[] = {
    {"iaval", interpolation_iaval, METH_VARARGS,
     "float iaval(x,[x1,x2,...,xn], [f(x1),f(x2),..,f(xn)])\n"
     "Neville's method for Lagrange's interpolation\n"
     "This function returns the value of the interpolation polynomial on the given point (x)." },
    {"divdif", interpolation_divdif, METH_VARARGS,
     "divdif([x1,x2,...,xn], [f(x1),f(x2),...,f(xn)])\n"
     "Newton's Divided differences method for Lagrange's Interpolation\n"
     "This function returns the divided differences of the Newton's divided differences interpolation polynomial."},
    {"hermite", interpolation_hermite, METH_VARARGS,
     "hermite([x1,x2,...,xn], [[f(x1),f'(x1),...,f(m1)(x1)],...,[f(xn),f'(xn),...,f(mn)(xn)]])\n"
     "Hermite's method for Lagrange's Interpolation\n"
     "This function returns the divided differences of the Hermite's divided differences interpolation polynomial."},
    {NULL, NULL}             /* sentinel */
};
 
DL_EXPORT(void) initinterpolation()
{
    Py_InitModule("interpolation", interpolationmethods);
}

Una vez escrito el código tendría que explicarlo... pero en serio, da pereza explicarlo, :p, mirad la referencia de Python online http://docs.python.org/ext/ext.html (Extendiendo Python, en inglés), http://docs.python.org/api/api.html (Python/C API Reference Manual). Después de entender lo que hemos escrito, tenemos que hacer que sea accesible desde el intérprete Python. Desde al versión 2.3 tenemos un método muy sencillo. Este se basa en crear una especie de "instalador" en un pequeño archivo Python que cuando es interpretado construye e instala el módulo para que pueda ser utilizado sin ningún problema.

1
2
3
4
5
6
7
8
9
from distutils.core import setup, Extension
 
module1 = Extension('interpolation',
                    sources = ['interpolationmodule.c'])
 
setup (name = 'interpolation',
       version = '0.1',
       description = 'Interpolation Package',
       ext_modules = [module1])

Por cierto, el fichero C lo llamé interpolationmodule.c, y a éste fichero Python lo llamaremos setup.py. Para construir el módulo escribimos python setup.py build . Para instalarlo debemos entrar en modo administrador y escribir lo siguiente: python setup.py install . Y con esto finaliza la instalación del módulo. Si queréis probarlo sólo hace falta escribir import interpolation en el intérprete, las funciones disponibles son iaval (método de Neville) y divdif (método de las diferencias divididas). Para los que queráis probar las diferecias de rendimiento, podéis importar el módulo timeit, que se utiliza como sigue.

Asignamos a una variable un objeto Timer:
t = timeit.Timer("interpolation.iaval(3.0, [2.0, 4.0], [5.0, 6.0])", "import interpolation")
Luego ejecutamos la función timeit() del objeto que hemos creado. Ésta función lo que hará será llamar a la función que le hemos indicado un millón de veces dentro del entorno que le indicamos en el segundo parámetro. Una vez acabe su trabajo, nos indicará el tiempo transcurrido. Si creamos una función en Python que haga el mismo trabajo y le damos los mismos datos, veremos con timeit la gran diferencia de rendimiento que hay entre ambas.

Bueno, acabo este artículo nada reflexivo, aunque espero, entretenido para frikis como yo.