MATLAB
Contenido
- 1 MEX functions
- 1.1 Eligiendo compilador
- 1.2 Un primer programa ‘Hola mundo’
- 1.3 Utilizando una función C existente para crear un archivo MEX
- 1.3.1 Creando el archivo fuente: La función mexFunction
- 1.3.2 Recibiendo los datos en la función mexFunction y preparando la salida
- 1.3.3 Agregando nuestra función C
- 1.3.4 Compilando nuestro archivo C con mex
- 1.3.5 Arreglos de tamaño variable
- 1.3.6 Validando entradas y salidas y añadiendo mensajes
- 1.3.7 Pidiendo un número al usuario
MEX functions
Un archivo MEX permite llamar una función escrita en C desde Matlab. Para ello se hace uso del comando de Matlab ‘mex’ y de un compilador compatible instalado en el sistema operativo.
Eligiendo compilador
Para el sistema operativo Linux la version por defecto del compilador es GCC versión ‘6.3.X’. La lista de compiladores compatibles de acuerdo al sistema instalado se puede consultar aquí. Para corroborar que su sistema posee un compilador compatible (o elegir entre más de un compilador, si su sistema los posee) ejecute lo siguiente:
>> mex -setup
MEX configured to use 'gcc' for C language compilation.
Un primer programa ‘Hola mundo’
El objetivo de esta seccion es compilar y ejecutar el contenido de un archivo C sencillo desde Matlab.
Pasos:
- 1. Utilizando su editor de texto favorito, cree un archivo llamado ‘hola.c’ en su área de trabajo de Matlab. Su área de trabajo actual la puede encontrar ejecutando
>> pwd
- 2. Agregue el siguiente contenido a su archivo ‘hola.c’
#include "mex.h" void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[]){ printf("Hola mundo!\n"); return; }
- Note que el header ‘mex.h’ es requerido en el archivo. Éste contiene las funciones del API de Matlab. Las demás secciones seran explicadas a lo largo de esta guía.
- 3.Una vez guardado el archivo, desde la consola de Matlab ejecute
>> mex hola.c Building with 'gcc'. MEX completed successfully.
- 4. Una vez que el archivo ha sido satisfactoriamente compilado la función MEX ‘hola’ estará disponible para usarse desde la consola de Matlab:
>> hola Hola mundo!
Utilizando una función C existente para crear un archivo MEX
Suponga que se tiene el siguiente código funcional en C:
#include<stdio.h> void arrayProduct(double x, double *y, double *z, int n); void main(){ double vect0[] = {3,2,5,4}; double cval = 4; double vect1[4]; arrayProduct(cval, vect0, vect1, 4); printf("Third scaled value is %3.2f\n", vect1[2]); } void arrayProduct(double x, double *y, double *z, int n){ int i; for (i=0; i<n; i++) { z[i] = x * y[i]; } }
el cual simplemente escala un vector dado por una constante, usando para ello la función arrayProduct. Se requiere crear un archivo MEX de manera que la funcionalidad de arrayProduct se pueda usar directamente desde Matlab, por ejemplo:
>>s = 5; >>A = [1.5, 2, 9]; >>B = arrayProduct(s,A) B = 7.5000 10.0000 45.0000
Creando el archivo fuente: La función mexFunction
En esta sección se creará el archivo fuente para ser usado por Matlab. Cree un archivo *.c y agregue el siguiente header para poder usar el API de Matlab (en este ejemplo, es nombrado como cvector.c)
#include "mex.h"
Lo siguiente es agregar la función mexFunction. Esta función es requerida en cualquier archivo *.c que se desee compilar con ‘mex’, y es el puente entre nuestro código C y Matlab. Cada vez que una función MEX es llamada Matlab busca en el código correspondiente a la función mexFunction. Si mexFunction no es encontrada entonces un error es disparado. La función mexFunction tiene la siguiente firma:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
Los argumentos se detallan en la siguiente tabla.
nlhs | Número de argumentos de salida, o bien el tamaño del arreglo plhs |
plhs | Arreglo de punteros a los argumentos de salida. |
nrhs | Número de argumentos de entrada, o bien el tamaño del arreglo prhs |
prhs | Arreglo de punteros a los argumentos de entrada. |
Note que los argumentos de entrada o salida son del tipo mxArray, el cual es el tipo del 'arreglo de Matlab' (En Matlab todas las variables (escalares, vectores, matrices, objetos …) son guardadas como arreglos. Al declararse una variable de Matlab en C este tipo de variable debe de ser explícito).
En nuestro caso, queremos definir un arreglo de tamaño arbitrario en Matlab y definir una constante para multiplicar cada entrada de este arreglo. Buscamos algo como
>> s = 5; >> A = [3,2,8]; >> B = cvector(s,A);
Entonces estaremos enviando por medio del argumento prhs la constante en la primera posición (s en el código anterior), y el vector en la segunda posición (A en el código anterior).
Recibiendo los datos en la función mexFunction y preparando la salida
Necesitamos entonces saber interpretar nuestro arreglo de entrada y preparar la salida de forma consistente con lo que se quiere hacer (funcion cvector en nuestro caso, figura 2). El código que debemos agregar nuestra función mexFunction se presenta a continuación.
void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){ double multiplier; /* input scalar */ double *inMatrix; /* 1xN input matrix */ mwSize ncols; /* size of matrix */ double *outMatrix; /* output matrix */ multiplier = mxGetScalar(prhs[0]); inMatrix = mxGetDoubles(prhs[1]); ncols = mxGetN(prhs[1]); /* create the output matrix */ plhs[0] = mxCreateDoubleMatrix(1,ncols,mxREAL); /* get a pointer to the real data in the output matrix */ outMatrix = mxGetDoubles(plhs[0]); /* call the computational routine */ arrayProduct(multiplier,inMatrix,outMatrix,ncols); }
En la variable multiplier guardaremos el escalar de entrada, que como sabemos de las especificaciones está dada en la primera posición del arreglo de entrada. Usamos la función de Matlab mxGetScalar que devuelve la parte real del primer elemento de un arreglo del tipo mxArray. Seguidamente recibimos el vector usando la función mxGetDoubles que devuelve los elementos reales de un arreglo, y la guardamos en inMatrix. Mas detalles sobre estas funciones pueden ser consultados acá.
El número de columnas es guardado en ncols. Note que esta variable es del tipo mwSize. El tipo mwSize es una macro que permite redefinir el tamaño del entero basado en la plataforma que se esté trabajando, lo cual brinda flexibilidad. Para ser consistente, la definción de i en la función arrayProduct será redefinido posteriormente al tipo mwSize también.
La cantidad de columnas del arreglo es guardada en ncols por medio de la función mxGetN que devuelve la cantidad de columnas de un arreglo.
Seguidamente se prepara la salida de nuestra función. Creamos primero el arreglo de salida plhs de una fila, n columnas y de tipo real. Las entradas reales de este arreglo las guardamos a continuación en outMatrix.
Nuestra función mexFunction está lista ahora para recibir las variables desde Matlab y devolver la salida.
Agregando nuestra función C
Finalmente, agregamos nuestra función C al archivo que vamos a compilar con mex.
#include "mex.h" void arrayProduct(double x, double *y, double *z, mwSize n){ mwSize i; for (i=0; i<n; i++) { z[i] = x * y[i]; } } void mexFunction( int nlhs, mxArray *plhs[], (…)
Note que la variable i se redifinió a mwSize, como se había anticipado.
Compilando nuestro archivo C con mex
Con nuestro archivo cvector.c listo procedemos a compilar con mex (La opción -R2018a es requerida porque en este ejemplo se han usado funciones del más reciente API de Matlab.)
>> mex cvector.c -R2018a Building with 'gcc'. MEX completed successfully.
En seguida, procedemos a crear un vector de ejemplo y una constante arbitraria para usar nuestra función MEX.
>> c = 3 c = 3 >> A = [2,5,6,3.5] A = 2.0000 5.0000 6.0000 3.5000 >> cvector(c,A) ans = 6.0000 15.0000 18.0000 10.5000
Arreglos de tamaño variable
Nótese que en el ejemplo anterior el tamaño del arreglo de entrada nunca se definió a algún valor fijo. Retomemos el código de nuestra mexFunction:
(...) double *inMatrix; /* 1xN input matrix */ mwSize ncols; /* size of matrix */ double *outMatrix; /* output matrix */ multiplier = mxGetScalar(prhs[0]); inMatrix = mxGetDoubles(prhs[1]); ncols = mxGetN(prhs[1]); (...)
Note que para recibir el vector de entrada se utiliza el puntero inMatrix. El vector de entrada recordemos que corresponde a la posición 1 del arreglo de punteros de entrada prhs, el cual es de tamaño arbitrario. Posteriormente el tamaño de este arreglo de entrada se obtiene con mxGetN el cual servirá luego como parámetro en nuestra función de C que multiplica el vector por el escalar.
En el ejemplo se utilizó un vector de entrada de 3 valores pero perfectamente se puede cambiar. Por ejemplo,
>> c = 2; >> B = [1,2,3,4,5,6,7,8,9]; >> cvector(c,B) ans = 2 4 6 8 10 12 14 16 18
Validando entradas y salidas y añadiendo mensajes
Una vez que nuestro código es funcional, podemos proceder a hacerlo más robusto agregando mensajes o bien validando el tipo y la cantidad de los argumentos de entrada y salida. Para asegurar que la entrada conste de 2 argumentos y la salida de 1 argumento añadimos el siguiente código en nuestra mexFunction:
if(nrhs != 2) { mexErrMsgIdAndTxt("MyToolbox:cvector:nrhs",”Dos entradas requeridas"); } if(nlhs != 1) { mexErrMsgIdAndTxt("MyToolbox:cvector:nlhs","Una salida requerida"); }
El siguiente código valida que el primer argumento sea un escalar del tipo double.
/* make sure the first input argument is scalar */ if( !mxIsDouble(prhs[0]) || mxGetNumberOfElements(prhs[0]) != 1 ) { mexErrMsgIdAndTxt("MyToolbox:cvector:notScalar", "Entrada debe ser un escalar"); }
En el código anterior se ha usado la función mxIsDouble que revisa si la entrada es del tipo double y además la función mxGetNumberOfElements que devuelve la cantidad de elementos de un array. Recuerde que prhs[0] contiene el escalar por el que queremos multiplicar el vector.
Similarmente, podemos revisar si el segundo argumento de entrada corresponde a un vector fila:
/* check that number of rows in second input argument is 1 */ if(mxGetM(prhs[1]) != 1) { mexErrMsgIdAndTxt("MyToolbox:cvector:notRowVector", "Entrada no es un vector fila"); }
En este caso se usó la función mxGetM la cual devuelve la cantidad de filas en un arreglo.
En el código anterior se ha usado la función mexErrMsgIdAndTxt, en la cual además se incluyó un “identificador de mensaje” el cual usa la sintáxis
componente:mnemotécnico
Para evitar el uso de un identificar de mensaje se puede usar la versión simplificada mexErrMsgTxt
mexErrMsgTxt("Entrada debe ser un escalar");
La función mexPrintf imprime mensajes a como lo hace printf en C. También se dispone de la función mexWarnMsgIdAndTxt para imprimir warnings. Este último permite notificar errores o advertencias sin terminar la función MEX.
Pidiendo un número al usuario
Otra de las funcionalidades básicas que puede ser deseable añadir a nuestra función es pedir un número al usuario. Las funciones de C scanf y printf no son compatibles con Matlab. En lugar, usaremos la función input de Matlab en nuestra función MEX
>> input('Digite un número') Digite un número5 ans = 5
El siguiente código muestra cómo podemos pedir un número al usuario desde nuestra función MEX:
#include "mex.h" #include "string.h" void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[] ){ mxArray *new_number, *str; double out; str = mxCreateString("Ingrese un número"); mexCallMATLAB(1,&new_number,1,&str,"input"); out = mxGetScalar(new_number); mexPrintf("El número ingresado es: %.0f ", out); mxDestroyArray(new_number); mxDestroyArray(str); return; }
Aquí hacemos uso de la función mexCallMATLAB que está definida como
int mexCallMATLAB(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[], const char *functionName);
donde los argumentos son análogos a los de la tabla 1. En nuestro caso, tenemos 1 argumento de salida (new_number) y 1 argumento de entrada (el texto con el mensaje que ve el usuario). Por medio de mxDestroyArray liberamos la memoria dinámica utilizada en los argumentos de entrada (NO debe usarse en los argumentos de salida).