package org.baderlab.csplugins.brainlib; import java.util.ArrayList; import java.util.List; /** * Copyright (c) 2005 Memorial Sloan-Kettering Cancer Center * * * * Code written by: Gary Bader * * Authors: Gary Bader, Chris Sander * * * * This library is free software; you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as published * * by the Free Software Foundation; either version 2.1 of the License, or * * any later version. * * * * This library is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF * * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. The software and * * documentation provided hereunder is on an "as is" basis, and * * Memorial Sloan-Kettering Cancer Center * * has no obligations to provide maintenance, support, * * updates, enhancements or modifications. In no event shall the * * Memorial Sloan-Kettering Cancer Center * * be liable to any party for direct, indirect, special, * * incidental or consequential damages, including lost profits, arising * * out of the use of this software and its documentation, even if * * Memorial Sloan-Kettering Cancer Center * * has been advised of the possibility of such damage. See * * the GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * * * User: GaryBader * * Date: Aug 4, 2005 * * Time: 8:26:57 PM */ /** * Implements a ragged array distance matrix for use with AvgLinkHierarchicalClustering * Only stores the lower triangle without the diagonal. Assumes the matrix is symmetric around the diagonal * and the diagonal is all zeros. */ public class DistanceMatrix { private double[] distanceMatrix = null; private int matrixDim = 0; private ArrayList labels = null; private double minimumDistance = Double.MAX_VALUE; private int mini = -1; private int minj = -1; /** * Initializes a distance matrix of the given dimension * * @param matrixDim The dimension of the matrix */ public DistanceMatrix(int matrixDim) { if (matrixDim < 2) { throw new RuntimeException("Distance matrix size must be larger than 1"); } this.matrixDim = matrixDim; //allocate enough space for lower triangle of the matrix minus the diagonal distanceMatrix = new double[(matrixDim * matrixDim - 1) / 2]; } /** * Gets the dimension of the matrix * */ public int getMatrixDimension() { return matrixDim; } /** * Put a value in the matrix at position (i,j) */ public void setValue(int i, int j, double value) { if (j >= i) { //don't store the diagonal or upper triangle return; } //calculate the position in the array given the lower triangular matrix coordinates distanceMatrix[((i * (i - 1)) / 2) + j] = value; } /** * Get a value from the matrix at position (i,j) */ public double getValue(int i, int j) { if (i == j) { //return 0.0 for the diagonal return 0.0; } if (j > i) { //convert to lower triangle - the matrix is assumed to be symmetric int oldj = j; j = i; i = oldj; } //calculate the position in the array given the lower triangular matrix coordinates return (distanceMatrix[((i * (i - 1)) / 2) + j]); } /** * Sets the labels of the elements in this matrix. The elements of the ArrayList must be * Strings corresponding to the elements in this matrix (in the same order) */ public void setLabels(ArrayList labels) { this.labels = labels; } /** * Gets the labels for the distanceMatrix * * @return An ArrayList containing Strings in the order corresponding to the rows or columns of the distance matrix */ public ArrayList getLabels() { return labels; } /** * Calculates a distance matrix given a distance metric implemented in the DistanceMetric class * Note: you need to extend the DistanceMetric class and override the 'calc' method for this * to do something useful. * * @param objectList The list of objects to use to calculate an NxN distance matrix * @param distanceMetric The distance metric to use */ public void calcDistances(List objectList, DistanceMetric distanceMetric) { //calculate the lower triangle of the distance matrix for (int i = 0; i < objectList.size(); i++) { Object object1 = (Object) objectList.get(i); for (int j = 0; j < i; j++) { Object object2 = (Object) objectList.get(j); double distance = distanceMetric.calc(object1, object2); if (distance < minimumDistance) { minimumDistance = distance; mini = i; minj = j; } this.setValue(i, j, distance); } } } /** * Returns the minimum distance in the distance matrix. */ public double getMinimumDistance() { return this.minimumDistance; } /** * Returns the "i" index of the matrix where (i,j) contains the minimum distance value */ public int getMinimumI() { return this.mini; } /** * Returns the "j" index of the matrix where (i,j) contains the minimum distance value */ public int getMinimumJ() { return this.minj; } /** * Returns an exact copy of this DistanceMatrix object */ public DistanceMatrix copy() { DistanceMatrix dm = new DistanceMatrix(this.matrixDim); System.arraycopy(this.distanceMatrix, 0, dm.distanceMatrix, 0, this.distanceMatrix.length); dm.setLabels(this.getLabels()); return dm; } //TODO: normalize 0..1 distance matrix for other distance metrics - hierarchical clustering alg makes this assumption /** * Return the string representation of this matrix */ public String toString() { StringBuffer sb = new StringBuffer(); String lineSep = System.getProperty("line.separator"); //header for (int i = 0; i < labels.size(); i++) { sb.append("\t" + (String) labels.get(i)); } sb.append(lineSep); //labels rows for (int i = 0; i < matrixDim; i++) { sb.append(labels.get(i) + "\t"); for (int j = 0; j < i; j++) { sb.append(this.getValue(i, j)); if (j < i) { //don't output a tab for the last one sb.append("\t"); } } sb.append(lineSep); } return (sb.toString()); } }