sábado 11 de octubre de 2008

Manejo de matrices en java con UJMP

Hola,

En mi trabajo estoy teniendo que enfrentarme a algunos retos que hasta entonces no me había planteado. Actualmente, estamos en una fase en la que tenemos que convertir un algoritmo genético de código Matlab a Java.

Debido a que Matlab funciona de forma que todo elemento o variable creada es una matriz y java tiene diferentes tipos de datos, y existen escalares y matrices por separado, tuvimos que buscar alguna solución de forma que pudiéramos tratar con matrices de forma más o menos dinámica y que permitiera ejecutar las típicas operaciones que se realizan con matrices.

La solución fue UJMP. Una API para Java que permite realizar operaciones con matrices de lo más variadas.

Actualmente dicha API está en desarrollo y la documentación que se puede encontrar al respecto es bastante escasa, sin embargo, tras ponerme en contacto con Holger Arndt, uno de sus creadores, he podido llegar a hacer cosas realmente interesantes.

Es por eso que en las siguientes líneas voy a comentar alguno de los aspectos más interesantes de esta API con la que he trabajado con la intención de que pueda servir de ayuda a alguien más en el futuro.

A continuación se plantearán los "problemas" y su solución con el código Java.

Manejo simple de las clases Matrix y ListMatrix

La clase Matrix permite crear una matriz "típica" tal y como se conoce de siempre de M filas y N columnas.

La clase ListMatrix básicamente lo que hace es generar una matriz en forma de lista (por defecto vertical = una columna y m filas).

Esto es bastante útil cuando queremos manejar datos que por defecto nos lo dan en filas y luego por ejemplo queremos crear una matriz a partir de varias de esas listas (ver más adelante).

¿Como crear una matriz de tamaño MxN rellena de ceros (M y N es de tipo long)?



Matrix m = MatrixFactory.zeros(M,N);



¿Y de unos?



Matrix m = MatrixFactory.ones(M,N);



Así mismo es posible que se metan valores aleatorios:



Matrix m = MatrixFactory.rand(M,N);



Como crear una ListMatrix

Basta simplemente con escribir:



ListMatrix matriz = new DefaultListMatrix();



Ejemplo:



ListMatrix matriz = new DefaultListMatrix();



Los métodos que luego tendrá este objeto matriz son los mismos que suele tener cualquier colección (ArrayList, LinkedList, etc).
Operaciones de matrices por escalares

La clase Matrix permite que se realicen varias operaciones aritméticas sobre una determinada matriz, o entre matrices.

Multiplicación:

Si queremos multiplicar una matriz por un escalar concreto:



Matrix a = MatrixFactory.rand(5,5);

Matrix b = a.times(ESCALAR);



Por ejemplo -1, 2, etc.

Si tenemos dos matrices A y B, que queremos que cada A[i,j] se multiplique por B[i,j] (es decir, que se multipliquen cada elemento de A por cada elemento de B y se guarde el resultado), podemos hacer:



Matrix a = MatrixFactory.rand(5,5);
Matrix b = MatrixFactory.rand(5,5);

Matrix c = a.times(b);



Esto NO está realizando un producto matricial.

División:

El mismo ejemplo se podría aplicar para la división, pero usando el método "divide()". Por ejemplo:

Para dividir todos los elementos de una matriz por un escalar:



Matrix a = MatrixFactory.rand(5,5);

Matrix b = a.divide(ESCALAR);



El mismo caso que la multiplicación de dos matrices A y B se utiliza con el método divide(). Misma sintaxis.

Suma:

Para sumar a una matriz, a todos sus elementos, un determinado escalar, se usa lo siguiente:



Matrix a = MatrixFactory.rand(5,5);

Matrix b = a.plus(ESCALAR);



Eso sumará a todos los elementos de la matriz el escalar proporcionado.

Resta:

Para la resta, es prácticamente igual, pero usando el método minus:



Matrix a = MatrixFactory.rand(5,5);

Matrix b = a.minus(ESCALAR);



Operaciones de matrices por matrices

Producto:

Desconozco aún como realizar el producto de dos matrices.

División:

Desconozco aún como realizar la división de dos matrices.

Suma:

La suma se hace exactamente igual que con escalares:



Matrix a = MatrixFactory.rand(5,5);
Matrix b = MatrixFactory.rand(5,5);
Matrix c = a.plus(b);



Resta:

Y la resta, otro tanto:



Matrix a = MatrixFactory.rand(5,5);
Matrix b = MatrixFactory.rand(5,5);
Matrix c = a.minus(b);



Otras operaciones

Producto de las filas o columnas de una matriz.

Este comando trata de emular el comando prod de Matlab. Este comando consiste en multiplicar los elementos cada columna para obtener un vector con el resultado de dichas multiplicaciones.

Un ejemplo en matlab:


A =

8 1 6
3 5 7
4 9 2

>> prod(A)

ans =

96 45 84

>>


Código en Java para realizar esto:



Matrix test = MatrixFactory.rand(3,3);
Matrix a = test.prod(Ret.NEW, Matrix.ROW, true);



Donde:

Ret.NEW: Especifica que se va a devolver una nueva matriz.
Matrix.Row: En teoría especifica que son las filas quien se va a multiplicar. No acabo de comprender muy bien porque con Matrix.Row hace "esto" ya que creo que debería ser con Matrix.Column. Quizás no he cogido bien el concepto de "prod" de Matlab.
true: Indica que se ignoren los valores no presentes.

Si quisiéramos hacer el contrario, es decir, que multiplicara los elementos de la fila, en Matlab haríamos:


>>> prod(A,2)

ans =

48
105
72



Y en UJMP:



Matrix test = MatrixFactory.rand(3,3);

Matrix a = test.prod(Ret.NEW, Matrix.COLUMN, true);



Seleccionar una determinada fila o columna en una matriz

Para seleccionar una determinada fila de una matriz:



Matrix ret = mat.selectRows(Ret.NEW, FILA);



Donde "mat" es la matriz de la que queremos obtener la fila y "FILA" el número de fila.

Para una columna, prácticamente igual:



Matrix ret = mat.selectColumns(Ret.NEW, FILA);



Sin embargo, estos dos métodos devuelven un objeto Matrix, y al obtener una sola fila o columna lo ideal sería poder almacenarlo en un ListMatrix. Actualmente esta opción no está implementada en UJMP, pero en la parte de abajo de este documento se encuentra una clase Java con varios métodos interesantes que permiten realizar esto mismo desarrollada por mi mismo.

Crear una matriz a partir de varios ListMatrix

Supongamos que tenemos varios ListMatrix (u otra colección: ArrayList, LinkedList) y queremos a partir de esa colección de elementos crear una matriz.

Supongamos este ejemplo:

S = [ 1 2 3 4 ]
R = [ 5 6 7 8 ]

Y queremos generar:



SR =

1 5
2 6
3 7
4 8


En primer lugar debemos generar "dos matrices" a partir de las distintas listas:

Recordar que "S" y "R" son ListMatrix o alguna colección de elementos:



Matrix ms = MatrixFactory.linkToCollection(S);
Matrix mr = MatrixFactory.linkToCollection(S);



A continuación debemos "juntar" eso:



Matrix sr = MatrixFactory.horCat(ms,mr);



El método horCat realiza una concatenación horizontal de ambos elementos. Si tratamos con ListMatrix debemos recordar que los ListMatrix tratan sus elementos como si fuera una matriz de n filas pero una columna, al realizar una concatenación horizontal de dos matrices de n filas y una columna sale como resultado lo indicado en la figura.

Si por el contrario quisiéramos que la figura quedara así:

SR=

1 2 3 4
5 6 7 8


Deberíamos usar el método vertCat:



Matrix sr = MatrixFactory.vertCat(ms,mr);



Realizar traspuesta de una matriz

Una aplicación interesante por lo general es realizar la traspuesta de una matriz. Para ello, la API de UJMP dispone de un método para hacerlo directamente:



Matrix m = MatrixFactory.rand(5,5);

Matrix p = m.transpose();



Otros

Si miramos los métodos disponibles para un objeto Matrix veremos que existen un montón de métodos que pueden ser bastante útiles como obtener sus dimensiones (getRowCount() & getColumnCount()), obtener el máximo y mínimo de la matriz, etc.

Otros métodos/operaciones interesantes que no vienen en UJMP

UJMP es una API excepcional, pero existen algunas funciones que aún no están implementadas pero que estoy seguro que en el futuro lo estarán. A continuación voy a poner algún ejemplo de algunas de estas funciones no implementadas y mi solución personal con una clase que he desarrollado llamada UJMPUtil.

NOTA: Esta clase fue diseñada para dar solución a un problema concreto, es por esto que los tipos que se han usado no han sido genéricos sino que se trabaja siempre con la clase Double dado que fue diseñada para resolver un problema concreto donde trabajar con este objeto era suficiente.

Si se quisiera hacer genérica o usarla con otro tipo de objeto habría que readaptarla.

Convertir Matrix a ListMatrix

En el ejemplo que comentábamos antes donde obteníamos una fila o columna de una matriz, pero esto devolvía una Matrix también comentábamos que podría ser interesante obtener directamente la matriz en forma de lista.

Esta operación es sumamente sencilla ya que basta con crear un objeto ListMatrix, recorrer el Matrix entero y cada elemento ir volcándolo a este nuevo ListMatrix.

Para hacer esto:



Matrix a = MatrixFactory.rand(3,3);
ListMatrix lm = UJMPUtil.convertMatrixToListMatrix(a);



Comparación de matrices

Matlab tiene una funcionalidad concreta y es que a la hora de comparar dos matrices A y B con operadores tipo mayor, menor, mayor o igual, menor o igual, igual, se puede guardar el resultado de esa comparación como una matriz en vez de como un simple "true" o "false".

Por ejemplo, suponiendo A y B dos matrices, si en Matlab hacemos:

C = A>=B

C será una matriz que estará llena de ceros y unos. Habrá un uno en cada C[i,j] si A[i,j] >= B[i,j] y un cero en caso contrario.

Básicamente de lo que se trata es de ir recorriendo ambas matrices e ir comparando sus valores posición a posición. Si cumplen la comparación, en la matriz resultado introducirá un uno, si no la cumplen introducirá un cero.

Dado que UJMP no parecía tener esta función, también se implementó a "mano".

Supongamos por lo tanto dos matrices "a" y "b":



Matrix a = MatrixFactory.rand(3,3);
Matrix b = MatrixFactory.rand(3,3);



Si queremos guardar en una matriz "c" el resultado de por ejemplo la comparación "a>=b", haremos:



Matrix c = UJMPUtil.compareMatrixesAsBoolean(a,b,UJMPUtil.COMPARATOR_GREATER_OR_EQUALS);



Y devolverá una matriz de ceros y unos dependiendo del resultado de la comparación.

Así mismo, los comparadores disponibles son:

< (Menor estricto): UJMPUtil.COMPARATOR_LOWER
> (Mayor estricto): UJMPUtil.COMPARATOR_GREATER
>= (Mayor o igual): UJMPUtil.COMPARATOR_GREATER_OR_EQUALS
<= (Menor o igual): UJMPUtil.COMPARATOR_LOWER_OR_EQUALS
!= (Distinto): UJMPUtil.COMPARATOR_DISTINCT
= (Igual) UJMPUtil.COMPARATOR_EQUALS

______________________________

UJMPUtil.java



import org.ujmp.core.Matrix;
import org.ujmp.core.MatrixFactory;
import org.ujmp.core.listmatrix.DefaultListMatrix;
import org.ujmp.core.listmatrix.ListMatrix;


/**
* La clase UJMPUtil contiene algunos métodos estáticos para realizar ciertas funciones
* que se usan en el código Matlab pero que en la API UJMP no están implentadas aún.
*
* Al ser una clase para un uso particular se establece que los objetos que contienen
* las matrices serán en todo momento de tipo "Double".
*
* @author jalo
*
*/
public class UJMPUtil {

public static final int COMPARATOR_GREATER = 0;
public static final int COMPARATOR_LOWER = 1;
public static final int COMPARATOR_EQUALS = 2;
public static final int COMPARATOR_GREATER_OR_EQUALS = 3;
public static final int COMPARATOR_LOWER_OR_EQUALS = 4;
public static final int COMPARATOR_DISTINCT = 5;

/**
* Método para convertir una Matriz (Matrix) en una ListMatrix.
* @param mat Recibe la matriz.
* @return Devuelve la ListMatrix.
*/
public static ListMatrix convertMatrixToListMatrix(Matrix mat) {
ListMatrix ret = new DefaultListMatrix();
for (int i = 0; i < mat.getRowCount(); i++) {
for (int j = 0; j < mat.getColumnCount(); j++) {
ret.add(mat.getAsDouble(i,j));
}
}
return ret;
}

/**
* Método para comparar dos matrices y devolver como resultado una matriz que contendrá
* en cada posición [i,j] si m1[i,j] -comparador- m2[i,j]. Donde -comparador- puede ser un comparador
* matemático de los siguientes: >, <, >=, <=, =
*
* En caso de que cumpla la comparación pondrá en la matriz resultante un uno. En caso contrario un cero.
*
* @param m1 Recibe la primera matriz.
* @param m2 Recibe la segunda matriz.
* @return Devuelve la matriz resultado.
* @throws NotSameDimensionsException Puede lanzar excepción si las matrices no son iguales.
*/
public static Matrix compareMatrixesAsBoolean(Matrix m1, Matrix m2, int comparator) throws NotSameDimensionsException {
if ((m1.getRowCount() == m2.getRowCount()) && (m1.getColumnCount() == m2.getColumnCount())) {
Matrix ret = MatrixFactory.zeros(m1.getRowCount(),m1.getColumnCount()); // Rellenamos con ceros inicialmente.
for (int i = 0; i < ret.getRowCount(); i++) {
for (int j = 0; j < ret.getColumnCount(); j++) {
switch (comparator) {
case COMPARATOR_LOWER:
if (m1.getAsDouble(i,j) < m2.getAsDouble(i,j)) {
ret.setAsInt(1, i,j); // Si lo cumple ponemos un uno.
}
break;
case COMPARATOR_GREATER:
if (m1.getAsDouble(i,j) > m2.getAsDouble(i,j)) {
ret.setAsInt(1, i,j); // Si lo cumple ponemos un uno.
}
break;
case COMPARATOR_EQUALS:
if (m1.getAsDouble(i,j) == m2.getAsDouble(i,j)) {
ret.setAsInt(1, i,j); // Si lo cumple ponemos un uno.
}
break;
case COMPARATOR_GREATER_OR_EQUALS:
if (m1.getAsDouble(i,j) >= m2.getAsDouble(i,j)) {
ret.setAsInt(1, i,j); // Si lo cumple ponemos un uno.
}
break;
case COMPARATOR_LOWER_OR_EQUALS:
if (m1.getAsDouble(i,j) <= m2.getAsDouble(i,j)) {
ret.setAsInt(1, i,j); // Si lo cumple ponemos un uno.
}
break;
case COMPARATOR_DISTINCT:
if (m1.getAsDouble(i,j) >= m2.getAsDouble(i,j)) {
ret.setAsInt(1, i,j); // Si lo cumple ponemos un uno.
}
break;
}
}
}
return ret;
}
else {
throw new NotSameDimensionsException();
}
}
}



Y de momento, esto es todo.

2 comentarios:

Dario ITL dijo...

muy buen articulo, pero como instalo las bibliotecas de UJMP?

jalo dijo...

Hola Dario,

Depende del IDE que uses, pero básicamente lo que debes de hacer es bajarte la UJMP y las librerías que te vienen dentro (los .jar) adjuntarlos a tu proyecto para que cuando importes las clases te las reconozca.

Ya te digo que depende del IDE.