weka.classifiers.functions.pace.PaceMatrix Maven / Gradle / Ivy
Show all versions of weka-stable Show documentation
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program 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. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/*
* PaceMatrix.java
* Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
*
*/
package weka.classifiers.functions.pace;
import weka.core.RevisionUtils;
import weka.core.matrix.DoubleVector;
import weka.core.matrix.FlexibleDecimalFormat;
import weka.core.matrix.IntVector;
import weka.core.matrix.Matrix;
import weka.core.matrix.Maths;
import java.util.Random;
import java.text.DecimalFormat;
/**
* Class for matrix manipulation used for pace regression.
*
* REFERENCES
*
* Wang, Y. (2000). "A new approach to fitting linear models in high
* dimensional spaces." PhD Thesis. Department of Computer Science,
* University of Waikato, New Zealand.
*
* Wang, Y. and Witten, I. H. (2002). "Modeling for optimal probability
* prediction." Proceedings of ICML'2002. Sydney.
*
* @author Yong Wang ([email protected])
* @version $Revision: 1.6 $
*/
public class PaceMatrix
extends Matrix {
/** for serialization */
static final long serialVersionUID = 2699925616857843973L;
/* ------------------------
Constructors
* ------------------------ */
/** Construct an m-by-n PACE matrix of zeros.
@param m Number of rows.
@param n Number of colums.
*/
public PaceMatrix( int m, int n ) {
super( m, n );
}
/** Construct an m-by-n constant PACE matrix.
@param m Number of rows.
@param n Number of colums.
@param s Fill the matrix with this scalar value.
*/
public PaceMatrix( int m, int n, double s ) {
super( m, n, s );
}
/** Construct a PACE matrix from a 2-D array.
@param A Two-dimensional array of doubles.
@throws IllegalArgumentException All rows must have the same length
*/
public PaceMatrix( double[][] A ) {
super( A );
}
/** Construct a PACE matrix quickly without checking arguments.
@param A Two-dimensional array of doubles.
@param m Number of rows.
@param n Number of colums.
*/
public PaceMatrix( double[][] A, int m, int n ) {
super( A, m, n );
}
/** Construct a PaceMatrix from a one-dimensional packed array
@param vals One-dimensional array of doubles, packed by columns (ala Fortran).
@param m Number of rows.
@throws IllegalArgumentException Array length must be a multiple of m.
*/
public PaceMatrix( double vals[], int m ) {
super( vals, m );
}
/** Construct a PaceMatrix with a single column from a DoubleVector
@param v DoubleVector
*/
public PaceMatrix( DoubleVector v ) {
this( v.size(), 1 );
setMatrix( 0, v.size()-1, 0, v );
}
/** Construct a PaceMatrix from a Matrix
@param X Matrix
*/
public PaceMatrix( Matrix X ) {
super( X.getRowDimension(), X.getColumnDimension() );
A = X.getArray();
}
/* ------------------------
Public Methods
* ------------------------ */
/** Set the row dimenion of the matrix
* @param rowDimension the row dimension
*/
public void setRowDimension( int rowDimension )
{
m = rowDimension;
}
/** Set the column dimenion of the matrix
* @param columnDimension the column dimension
*/
public void setColumnDimension( int columnDimension )
{
n = columnDimension;
}
/**
* Clone the PaceMatrix object.
*
* @return the clone
*/
public Object clone () {
PaceMatrix X = new PaceMatrix(m,n);
double[][] C = X.getArray();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
C[i][j] = A[i][j];
}
}
return (Object) X;
}
/** Add a value to an element and reset the element
* @param i the row number of the element
* @param j the column number of the element
* @param s the double value to be added with
*/
public void setPlus(int i, int j, double s) {
A[i][j] += s;
}
/** Multiply a value with an element and reset the element
* @param i the row number of the element
* @param j the column number of the element
* @param s the double value to be multiplied with
*/
public void setTimes(int i, int j, double s) {
A[i][j] *= s;
}
/** Set the submatrix A[i0:i1][j0:j1] with a same value
* @param i0 the index of the first element of the column
* @param i1 the index of the last element of the column
* @param j0 the index of the first column
* @param j1 the index of the last column
* @param s the value to be set to
*/
public void setMatrix( int i0, int i1, int j0, int j1, double s ) {
try {
for( int i = i0; i <= i1; i++ ) {
for( int j = j0; j <= j1; j++ ) {
A[i][j] = s;
}
}
} catch( ArrayIndexOutOfBoundsException e ) {
throw new ArrayIndexOutOfBoundsException( "Index out of bounds" );
}
}
/** Set the submatrix A[i0:i1][j] with the values stored in a
* DoubleVector
* @param i0 the index of the first element of the column
* @param i1 the index of the last element of the column
* @param j the index of the column
* @param v the vector that stores the values*/
public void setMatrix( int i0, int i1, int j, DoubleVector v ) {
for( int i = i0; i <= i1; i++ ) {
A[i][j] = v.get(i-i0);
}
}
/** Set the whole matrix from a 1-D array
* @param v 1-D array of doubles
* @param columnFirst Whether to fill the column first or the row.
* @throws ArrayIndexOutOfBoundsException Submatrix indices
*/
public void setMatrix ( double[] v, boolean columnFirst ) {
try {
if( v.length != m * n )
throw new IllegalArgumentException("sizes not match.");
int i, j, count = 0;
if( columnFirst ) {
for( i = 0; i < m; i++ ) {
for( j = 0; j < n; j++ ) {
A[i][j] = v[count];
count ++;
}
}
}
else {
for( j = 0; j < n; j++ ) {
for( i = 0; i < m; i++ ){
A[i][j] = v[count];
count ++;
}
}
}
} catch( ArrayIndexOutOfBoundsException e ) {
throw new ArrayIndexOutOfBoundsException( "Submatrix indices" );
}
}
/** Returns the maximum absolute value of all elements
@return the maximum value
*/
public double maxAbs () {
double ma = Math.abs(A[0][0]);
for (int j = 0; j < n; j++) {
for (int i = 0; i < m; i++) {
ma = Math.max(ma, Math.abs(A[i][j]));
}
}
return ma;
}
/** Returns the maximum absolute value of some elements of a column,
that is, the elements of A[i0:i1][j].
@param i0 the index of the first element of the column
@param i1 the index of the last element of the column
@param j the index of the column
@return the maximum value */
public double maxAbs ( int i0, int i1, int j ) {
double m = Math.abs(A[i0][j]);
for (int i = i0+1; i <= i1; i++) {
m = Math.max(m, Math.abs(A[i][j]));
}
return m;
}
/** Returns the minimum absolute value of some elements of a column,
that is, the elements of A[i0:i1][j].
@param i0 the index of the first element of the column
@param i1 the index of the last element of the column
@param column the index of the column
@return the minimum value
*/
public double minAbs ( int i0, int i1, int column ) {
double m = Math.abs(A[i0][column]);
for (int i = i0+1; i <= i1; i++) {
m = Math.min(m, Math.abs(A[i][column]));
}
return m;
}
/** Check if the matrix is empty
* @return true if the matrix is empty
*/
public boolean isEmpty(){
if(m == 0 || n == 0) return true;
if(A == null) return true;
return false;
}
/** Return a DoubleVector that stores a column of the matrix
* @param j the index of the column
* @return the column
*/
public DoubleVector getColumn( int j ) {
DoubleVector v = new DoubleVector( m );
double [] a = v.getArray();
for(int i = 0; i < m; i++)
a[i] = A[i][j];
return v;
}
/** Return a DoubleVector that stores some elements of a column of the
* matrix
* @param i0 the index of the first element of the column
* @param i1 the index of the last element of the column
* @param j the index of the column
* @return the DoubleVector
*/
public DoubleVector getColumn( int i0, int i1, int j ) {
DoubleVector v = new DoubleVector( i1-i0+1 );
double [] a = v.getArray();
int count = 0;
for( int i = i0; i <= i1; i++ ) {
a[count] = A[i][j];
count++;
}
return v;
}
/** Multiplication between a row (or part of a row) of the first matrix
* and a column (or part or a column) of the second matrix.
* @param i the index of the row in the first matrix
* @param j0 the index of the first column in the first matrix
* @param j1 the index of the last column in the first matrix
* @param B the second matrix
* @param l the index of the column in the second matrix
* @return the result of the multiplication
*/
public double times( int i, int j0, int j1, PaceMatrix B, int l ) {
double s = 0.0;
for(int j = j0; j <= j1; j++ ) {
s += A[i][j] * B.A[j][l];
}
return s;
}
/** Decimal format for converting a matrix into a string
* @return the default decimal format
*/
protected DecimalFormat [] format() {
return format(0, m-1, 0, n-1, 7, false );
}
/** Decimal format for converting a matrix into a string
* @param digits the number of digits
* @return the decimal format
*/
protected DecimalFormat [] format( int digits ) {
return format(0, m-1, 0, n-1, digits, false);
}
/** Decimal format for converting a matrix into a string
* @param digits the number of digits
* @param trailing
* @return the decimal format
*/
protected DecimalFormat [] format( int digits, boolean trailing ) {
return format(0, m-1, 0, n-1, digits, trailing);
}
/** Decimal format for converting a matrix into a string
* @param i0
* @param i1
* @param j
* @param digits the number of digits
* @param trailing
* @return the decimal format
*/
protected DecimalFormat format(int i0, int i1, int j, int digits,
boolean trailing) {
FlexibleDecimalFormat df = new FlexibleDecimalFormat(digits, trailing);
df.grouping( true );
for(int i = i0; i <= i1; i ++ )
df.update( A[i][j] );
return df;
}
/** Decimal format for converting a matrix into a string
* @param i0
* @param i1
* @param j0
* @param j1
* @param trailing
* @param digits the number of digits
* @return the decimal format
*/
protected DecimalFormat [] format(int i0, int i1, int j0, int j1,
int digits, boolean trailing) {
DecimalFormat [] f = new DecimalFormat[j1-j0+1];
for( int j = j0; j <= j1; j++ ) {
f[j] = format(i0, i1, j, digits, trailing);
}
return f;
}
/**
* Converts matrix to string
*
* @return the matrix as string
*/
public String toString() {
return toString( 5, false );
}
/**
* Converts matrix to string
*
* @param digits number of digits after decimal point
* @param trailing true if trailing zeros are padded
* @return the matrix as string
*/
public String toString( int digits, boolean trailing ) {
if( isEmpty() ) return "null matrix";
StringBuffer text = new StringBuffer();
DecimalFormat [] nf = format( digits, trailing );
int numCols = 0;
int count = 0;
int width = 80;
int lenNumber;
int [] nCols = new int[n];
int nk=0;
for( int j = 0; j < n; j++ ) {
lenNumber = nf[j].format( A[0][j]).length();
if( count + 1 + lenNumber > width -1 ) {
nCols[nk++] = numCols;
count = 0;
numCols = 0;
}
count += 1 + lenNumber;
++numCols;
}
nCols[nk] = numCols;
nk = 0;
for( int k = 0; k < n; ) {
for( int i = 0; i < m; i++ ) {
for( int j = k; j < k + nCols[nk]; j++)
text.append( " " + nf[j].format( A[i][j]) );
text.append("\n");
}
k += nCols[nk];
++nk;
text.append("\n");
}
return text.toString();
}
/** Squared sum of a column or row in a matrix
* @param j the index of the column or row
* @param i0 the index of the first element
* @param i1 the index of the last element
* @param col if true, sum over a column; otherwise, over a row
* @return the squared sum
*/
public double sum2( int j, int i0, int i1, boolean col ) {
double s2 = 0;
if( col ) { // column
for( int i = i0; i <= i1; i++ )
s2 += A[i][j] * A[i][j];
}
else {
for( int i = i0; i <= i1; i++ )
s2 += A[j][i] * A[j][i];
}
return s2;
}
/** Squared sum of columns or rows of a matrix
* @param col if true, sum over columns; otherwise, over rows
* @return the squared sum
*/
public double[] sum2( boolean col ) {
int l = col ? n : m;
int p = col ? m : n;
double [] s2 = new double[l];
for( int i = 0; i < l; i++ )
s2[i] = sum2( i, 0, p-1, col );
return s2;
}
/** Constructs single Householder transformation for a column
*
@param j the index of the column
@param k the index of the row
@return d and q
*/
public double [] h1( int j, int k ) {
double dq[] = new double[2];
double s2 = sum2(j, k, m-1, true);
dq[0] = A[k][j] >= 0 ? - Math.sqrt( s2 ) : Math.sqrt( s2 );
A[k][j] -= dq[0];
dq[1] = A[k][j] * dq[0];
return dq;
}
/** Performs single Householder transformation on one column of a matrix
*
@param j the index of the column
@param k the index of the row
@param q q = - u'u/2; must be negative
@param b the matrix to be transformed
@param l the column of the matrix b
*/
public void h2( int j, int k, double q, PaceMatrix b, int l ) {
double s = 0, alpha;
for( int i = k; i < m; i++ )
s += A[i][j] * b.A[i][l];
alpha = s / q;
for( int i = k; i < m; i++ )
b.A[i][l] += alpha * A[i][j];
}
/** Constructs the Givens rotation
* @param a
* @param b
* @return a double array that stores the cosine and sine values
*/
public double [] g1( double a, double b ) {
double cs[] = new double[2];
double r = Maths.hypot(a, b);
if( r == 0.0 ) {
cs[0] = 1;
cs[1] = 0;
}
else {
cs[0] = a / r;
cs[1] = b / r;
}
return cs;
}
/** Performs the Givens rotation
* @param cs a array storing the cosine and sine values
* @param i0 the index of the row of the first element
* @param i1 the index of the row of the second element
* @param j the index of the column
*/
public void g2( double cs[], int i0, int i1, int j ){
double w = cs[0] * A[i0][j] + cs[1] * A[i1][j];
A[i1][j] = - cs[1] * A[i0][j] + cs[0] * A[i1][j];
A[i0][j] = w;
}
/** Forward ordering of columns in terms of response explanation. On
* input, matrices A and b are already QR-transformed. The indices of
* transformed columns are stored in the pivoting vector.
*
*@param b the PaceMatrix b
*@param pvt the pivoting vector
*@param k0 the first k0 columns (in pvt) of A are not to be changed
**/
public void forward( PaceMatrix b, IntVector pvt, int k0 ) {
for( int j = k0; j < Math.min(pvt.size(), m); j++ ) {
steplsqr( b, pvt, j, mostExplainingColumn(b, pvt, j), true );
}
}
/** Returns the index of the column that has the largest (squared)
* response, when each of columns pvt[ks:] is moved to become the
* ks-th column. On input, A and b are both QR-transformed.
*
* @param b response
* @param pvt pivoting index of A
* @param ks columns pvt[ks:] of A are to be tested
* @return the index of the column
*/
public int mostExplainingColumn( PaceMatrix b, IntVector pvt, int ks ) {
double val;
int [] p = pvt.getArray();
double ma = columnResponseExplanation( b, pvt, ks, ks );
int jma = ks;
for( int i = ks+1; i < pvt.size(); i++ ) {
val = columnResponseExplanation( b, pvt, i, ks );
if( val > ma ) {
ma = val;
jma = i;
}
}
return jma;
}
/** Backward ordering of columns in terms of response explanation. On
* input, matrices A and b are already QR-transformed. The indices of
* transformed columns are stored in the pivoting vector.
*
* A and b must have the same number of rows, being the (pseudo-)rank.
*
* @param b PaceMatrix b
* @param pvt pivoting vector
* @param ks number of QR-transformed columns; psuedo-rank of A
* @param k0 first k0 columns in pvt[] are not to be ordered.
*/
public void backward( PaceMatrix b, IntVector pvt, int ks, int k0 ) {
for( int j = ks; j > k0; j-- ) {
steplsqr( b, pvt, j, leastExplainingColumn(b, pvt, j, k0), false );
}
}
/** Returns the index of the column that has the smallest (squared)
* response, when the column is moved to become the (ks-1)-th
* column. On input, A and b are both QR-transformed.
*
* @param b response
* @param pvt pivoting index of A
* @param ks psudo-rank of A
* @param k0 A[][pvt[0:(k0-1)]] are excluded from the testing.
* @return the index of the column
*/
public int leastExplainingColumn( PaceMatrix b, IntVector pvt, int ks,
int k0 ) {
double val;
int [] p = pvt.getArray();
double mi = columnResponseExplanation( b, pvt, ks-1, ks );
int jmi = ks-1;
for( int i = k0; i < ks - 1; i++ ) {
val = columnResponseExplanation( b, pvt, i, ks );
if( val <= mi ) {
mi = val;
jmi = i;
}
}
return jmi;
}
/** Returns the squared ks-th response value if the j-th column becomes
* the ks-th after orthogonal transformation. A[][pvt[ks:j]] (or
* A[][pvt[j:ks]], if ks > j) and b[] are already QR-transformed
* on input and will remain unchanged on output.
*
* More generally, it returns the inner product of the corresponding
* row vector of the response PaceMatrix. (To be implemented.)
*
*@param b PaceMatrix b
*@param pvt pivoting vector
*@param j the column A[pvt[j]][] is to be moved
*@param ks the target column A[pvt[ks]][]
*@return the squared response value */
public double columnResponseExplanation( PaceMatrix b, IntVector pvt,
int j, int ks ) {
/* Implementation:
*
* If j == ks - 1, returns the squared ks-th response directly.
*
* If j > ks -1, returns the ks-th response after
* Householder-transforming the j-th column and the response.
*
* If j < ks - 1, returns the ks-th response after a sequence of
* Givens rotations starting from the j-th row. */
int k, l;
double [] xxx = new double[n];
int [] p = pvt.getArray();
double val;
if( j == ks -1 ) val = b.A[j][0];
else if( j > ks - 1 ) {
int jm = Math.min(n-1, j);
DoubleVector u = getColumn(ks,jm,p[j]);
DoubleVector v = b.getColumn(ks,jm,0);
val = v.innerProduct(u) / u.norm2();
}
else { // ks > j
for( k = j+1; k < ks; k++ ) // make a copy of A[j][]
xxx[k] = A[j][p[k]];
val = b.A[j][0];
double [] cs;
for( k = j+1; k < ks; k++ ) {
cs = g1( xxx[k], A[k][p[k]] );
for( l = k+1; l < ks; l++ )
xxx[l] = - cs[1] * xxx[l] + cs[0] * A[k][p[l]];
val = - cs[1] * val + cs[0] * b.A[k][0];
}
}
return val * val; // or inner product in later implementation???
}
/**
* QR transformation for a least squares problem
* A x = b
* implicitly both A and b are transformed. pvt.size() is the psuedo-rank of
* A.
*
* @param b PaceMatrix b
* @param pvt pivoting vector
* @param k0 the first k0 columns of A (indexed by pvt) are pre-chosen.
* (But subject to rank examination.)
*
* For example, the constant term may be reserved, in which
* case k0 = 1.
**/
public void lsqr( PaceMatrix b, IntVector pvt, int k0 ) {
final double TINY = 1e-15;
int [] p = pvt.getArray();
int ks = 0; // psuedo-rank
for(int j = 0; j < k0; j++ ) // k0 pre-chosen columns
if( sum2(p[j],ks,m-1,true) > TINY ){ // large diagonal element
steplsqr(b, pvt, ks, j, true);
ks++;
}
else { // collinear column
pvt.shiftToEnd( j );
pvt.setSize(pvt.size()-1);
k0--;
j--;
}
// initial QR transformation
for(int j = k0; j < Math.min( pvt.size(), m ); j++ ) {
if( sum2(p[j], ks, m-1, true) > TINY ) {
steplsqr(b, pvt, ks, j, true);
ks++;
}
else { // collinear column
pvt.shiftToEnd( j );
pvt.setSize(pvt.size()-1);
j--;
}
}
b.m = m = ks; // reset number of rows
pvt.setSize( ks );
}
/** QR transformation for a least squares problem
* A x = b
* implicitly both A and b are transformed. pvt.size() is the psuedo-rank of A.
*
* @param b PaceMatrix b
* @param pvt pivoting vector
* @param k0 the first k0 columns of A (indexed by pvt) are pre-chosen.
* (But subject to rank examination.)
*
* For example, the constant term may be reserved, in which
* case k0 = 1.
**/
public void lsqrSelection( PaceMatrix b, IntVector pvt, int k0 ) {
int numObs = m; // number of instances
int numXs = pvt.size();
lsqr( b, pvt, k0 );
if( numXs > 200 || numXs > numObs ) { // too many columns.
forward(b, pvt, k0);
}
backward(b, pvt, pvt.size(), k0);
}
/**
* Sets all diagonal elements to be positive (or nonnegative) without
* changing the least squares solution
* @param Y the response
* @param pvt the pivoted column index
*/
public void positiveDiagonal( PaceMatrix Y, IntVector pvt ) {
int [] p = pvt.getArray();
for( int i = 0; i < pvt.size(); i++ ) {
if( A[i][p[i]] < 0.0 ) {
for( int j = i; j < pvt.size(); j++ )
A[i][p[j]] = - A[i][p[j]];
Y.A[i][0] = - Y.A[i][0];
}
}
}
/** Stepwise least squares QR-decomposition of the problem
* A x = b
@param b PaceMatrix b
@param pvt pivoting vector
@param ks number of transformed columns
@param j pvt[j], the column to adjoin or delete
@param adjoin to adjoin if true; otherwise, to delete */
public void steplsqr( PaceMatrix b, IntVector pvt, int ks, int j,
boolean adjoin ) {
final int kp = pvt.size(); // number of columns under consideration
int [] p = pvt.getArray();
if( adjoin ) { // adjoining
int pj = p[j];
pvt.swap( ks, j );
double dq[] = h1( pj, ks );
int pk;
for( int k = ks+1; k < kp; k++ ){
pk = p[k];
h2( pj, ks, dq[1], this, pk);
}
h2( pj, ks, dq[1], b, 0 ); // for matrix. ???
A[ks][pj] = dq[0];
for( int k = ks+1; k < m; k++ )
A[k][pj] = 0;
}
else { // removing
int pj = p[j];
for( int i = j; i < ks-1; i++ )
p[i] = p[i+1];
p[ks-1] = pj;
double [] cs;
for( int i = j; i < ks-1; i++ ){
cs = g1( A[i][p[i]], A[i+1][p[i]] );
for( int l = i; l < kp; l++ )
g2( cs, i, i+1, p[l] );
for( int l = 0; l < b.n; l++ )
b.g2( cs, i, i+1, l );
}
}
}
/** Solves upper-triangular equation
* R x = b
* On output, the solution is stored in b
* @param b the response
* @param pvt the pivoting vector
* @param kp the number of the first columns involved
*/
public void rsolve( PaceMatrix b, IntVector pvt, int kp) {
if(kp == 0) b.m = 0;
int i, j, k;
int [] p = pvt.getArray();
double s;
double [][] ba = b.getArray();
for( k = 0; k < b.n; k++ ) {
ba[kp-1][k] /= A[kp-1][p[kp-1]];
for( i = kp - 2; i >= 0; i-- ){
s = 0;
for( j = i + 1; j < kp; j++ )
s += A[i][p[j]] * ba[j][k];
ba[i][k] -= s;
ba[i][k] /= A[i][p[i]];
}
}
b.m = kp;
}
/** Returns a new matrix which binds two matrices together with rows.
* @param b the second matrix
* @return the combined matrix
*/
public PaceMatrix rbind( PaceMatrix b ){
if( n != b.n )
throw new IllegalArgumentException("unequal numbers of rows.");
PaceMatrix c = new PaceMatrix( m + b.m, n );
c.setMatrix( 0, m - 1, 0, n - 1, this );
c.setMatrix( m, m + b.m - 1, 0, n - 1, b );
return c;
}
/** Returns a new matrix which binds two matrices with columns.
* @param b the second matrix
* @return the combined matrix
*/
public PaceMatrix cbind( PaceMatrix b ) {
if( m != b.m )
throw new IllegalArgumentException("unequal numbers of rows: " +
m + " and " + b.m);
PaceMatrix c = new PaceMatrix(m, n + b.n);
c.setMatrix( 0, m - 1, 0, n - 1, this );
c.setMatrix( 0, m - 1, n, n + b.n - 1, b );
return c;
}
/** Solves the nonnegative linear squares problem. That is,
*
min || A x - b||, subject to x >= 0.
*
* For algorithm, refer to P161, Chapter 23 of C. L. Lawson and
* R. J. Hanson (1974). "Solving Least Squares
* Problems". Prentice-Hall.
* @param b the response
* @param pvt vector storing pivoting column indices
* @return solution */
public DoubleVector nnls( PaceMatrix b, IntVector pvt ) {
int j, t, counter = 0, jm = -1, n = pvt.size();
double ma, max, alpha, wj;
int [] p = pvt.getArray();
DoubleVector x = new DoubleVector( n );
double [] xA = x.getArray();
PaceMatrix z = new PaceMatrix(n, 1);
PaceMatrix bt;
// step 1
int kp = 0; // #variables in the positive set P
while ( true ) { // step 2
if( ++counter > 3*n ) // should never happen
throw new RuntimeException("Does not converge");
t = -1;
max = 0.0;
bt = new PaceMatrix( b.transpose() );
for( j = kp; j <= n-1; j++ ) { // W = A' (b - A x)
wj = bt.times( 0, kp, m-1, this, p[j] );
if( wj > max ) { // step 4
max = wj;
t = j;
}
}
// step 3
if ( t == -1) break; // optimum achieved
// step 5
pvt.swap( kp, t ); // move variable from set Z to set P
kp++;
xA[kp-1] = 0;
steplsqr( b, pvt, kp-1, kp-1, true );
// step 6
ma = 0;
while ( ma < 1.5 ) {
for( j = 0; j <= kp-1; j++ ) z.A[j][0] = b.A[j][0];
rsolve(z, pvt, kp);
ma = 2; jm = -1;
for( j = 0; j <= kp-1; j++ ) { // step 7, 8 and 9
if( z.A[j][0] <= 0.0 ) { // alpha always between 0 and 1
alpha = xA[j] / ( xA[j] - z.A[j][0] );
if( alpha < ma ) {
ma = alpha; jm = j;
}
}
}
if( ma > 1.5 )
for( j = 0; j <= kp-1; j++ ) xA[j] = z.A[j][0]; // step 7
else {
for( j = kp-1; j >= 0; j-- ) { // step 10
// Modified to avoid round-off error (which seemingly
// can cause infinite loop).
if( j == jm ) { // step 11
xA[j] = 0.0;
steplsqr( b, pvt, kp, j, false );
kp--; // move variable from set P to set Z
}
else xA[j] += ma * ( z.A[j][0] - xA[j] );
}
}
}
}
x.setSize(kp);
pvt.setSize(kp);
return x;
}
/** Solves the nonnegative least squares problem with equality
* constraint. That is,
*
min ||A x - b||, subject to x >= 0 and c x = d.
*
* @param b the response
* @param c coeficients of equality constraints
* @param d constants of equality constraints
* @param pvt vector storing pivoting column indices
* @return the solution
*/
public DoubleVector nnlse( PaceMatrix b, PaceMatrix c, PaceMatrix d,
IntVector pvt ) {
double eps = 1e-10 * Math.max( c.maxAbs(), d.maxAbs() ) /
Math.max( maxAbs(), b.maxAbs() );
PaceMatrix e = c.rbind( new PaceMatrix( times(eps) ) );
PaceMatrix f = d.rbind( new PaceMatrix( b.times(eps) ) );
return e.nnls( f, pvt );
}
/** Solves the nonnegative least squares problem with equality
* constraint. That is,
*
min ||A x - b||, subject to x >= 0 and || x || = 1.
*
* @param b the response
* @param pvt vector storing pivoting column indices
* @return the solution
*/
public DoubleVector nnlse1( PaceMatrix b, IntVector pvt ) {
PaceMatrix c = new PaceMatrix( 1, n, 1 );
PaceMatrix d = new PaceMatrix( 1, b.n, 1 );
return nnlse(b, c, d, pvt);
}
/** Generate matrix with standard-normally distributed random elements
@param m Number of rows.
@param n Number of colums.
@return An m-by-n matrix with random elements. */
public static Matrix randomNormal( int m, int n ) {
Random random = new Random();
Matrix A = new Matrix(m,n);
double[][] X = A.getArray();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
X[i][j] = random.nextGaussian();
}
}
return A;
}
/**
* Returns the revision string.
*
* @return the revision
*/
public String getRevision() {
return RevisionUtils.extract("$Revision: 1.6 $");
}
/**
* for testing only
*
* @param args the commandline arguments - ignored
*/
public static void main( String args[] ) {
System.out.println("================================================" +
"===========");
System.out.println("To test the pace estimators of linear model\n" +
"coefficients.\n");
double sd = 2; // standard deviation of the random error term
int n = 200; // total number of observations
double beta0 = 100; // intercept
int k1 = 20; // number of coefficients of the first cluster
double beta1 = 0; // coefficient value of the first cluster
int k2 = 20; // number of coefficients of the second cluster
double beta2 = 5; // coefficient value of the second cluster
int k = 1 + k1 + k2;
DoubleVector beta = new DoubleVector( 1 + k1 + k2 );
beta.set( 0, beta0 );
beta.set( 1, k1, beta1 );
beta.set( k1+1, k1+k2, beta2 );
System.out.println("The data set contains " + n +
" observations plus " + (k1 + k2) +
" variables.\n\nThe coefficients of the true model"
+ " are:\n\n" + beta );
System.out.println("\nThe standard deviation of the error term is " +
sd );
System.out.println("==============================================="
+ "============");
PaceMatrix X = new PaceMatrix( n, k1+k2+1 );
X.setMatrix( 0, n-1, 0, 0, 1 );
X.setMatrix( 0, n-1, 1, k1+k2, random(n, k1+k2) );
PaceMatrix Y = new
PaceMatrix( X.times( new PaceMatrix(beta) ).
plusEquals( randomNormal(n,1).times(sd) ) );
IntVector pvt = (IntVector) IntVector.seq(0, k1+k2);
/*System.out.println( "The OLS estimate (by jama.Matrix.solve()) is:\n\n" +
(new PaceMatrix(X.solve(Y))).getColumn(0) );*/
X.lsqrSelection( Y, pvt, 1 );
X.positiveDiagonal( Y, pvt );
PaceMatrix sol = (PaceMatrix) Y.clone();
X.rsolve( sol, pvt, pvt.size() );
DoubleVector betaHat = sol.getColumn(0).unpivoting( pvt, k );
System.out.println( "\nThe OLS estimate (through lsqr()) is: \n\n" +
betaHat );
System.out.println( "\nQuadratic loss of the OLS estimate (||X b - X bHat||^2) = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaHat)) )))
.getColumn(0).sum2() );
System.out.println("=============================================" +
"==============");
System.out.println(" *** Pace estimation *** \n");
DoubleVector r = Y.getColumn( pvt.size(), n-1, 0);
double sde = Math.sqrt(r.sum2() / r.size());
System.out.println( "Estimated standard deviation = " + sde );
DoubleVector aHat = Y.getColumn( 0, pvt.size()-1, 0).times( 1./sde );
System.out.println("\naHat = \n" + aHat );
System.out.println("\n========= Based on chi-square mixture ============");
ChisqMixture d2 = new ChisqMixture();
int method = MixtureDistribution.NNMMethod;
DoubleVector AHat = aHat.square();
d2.fit( AHat, method );
System.out.println( "\nEstimated mixing distribution is:\n" + d2 );
DoubleVector ATilde = d2.pace2( AHat );
DoubleVector aTilde = ATilde.sqrt().times(aHat.sign());
PaceMatrix YTilde = new
PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
X.rsolve( YTilde, pvt, pvt.size() );
DoubleVector betaTilde =
YTilde.getColumn(0).unpivoting( pvt, k );
System.out.println( "\nThe pace2 estimate of coefficients = \n" +
betaTilde );
System.out.println( "Quadratic loss = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaTilde)) )))
.getColumn(0).sum2() );
ATilde = d2.pace4( AHat );
aTilde = ATilde.sqrt().times(aHat.sign());
YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
X.rsolve( YTilde, pvt, pvt.size() );
betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
System.out.println( "\nThe pace4 estimate of coefficients = \n" +
betaTilde );
System.out.println( "Quadratic loss = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaTilde)) )))
.getColumn(0).sum2() );
ATilde = d2.pace6( AHat );
aTilde = ATilde.sqrt().times(aHat.sign());
YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
X.rsolve( YTilde, pvt, pvt.size() );
betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
System.out.println( "\nThe pace6 estimate of coefficients = \n" +
betaTilde );
System.out.println( "Quadratic loss = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaTilde)) )))
.getColumn(0).sum2() );
System.out.println("\n========= Based on normal mixture ============");
NormalMixture d = new NormalMixture();
d.fit( aHat, method );
System.out.println( "\nEstimated mixing distribution is:\n" + d );
aTilde = d.nestedEstimate( aHat );
YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
X.rsolve( YTilde, pvt, pvt.size() );
betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
System.out.println( "The nested estimate of coefficients = \n" +
betaTilde );
System.out.println( "Quadratic loss = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaTilde)) )))
.getColumn(0).sum2() );
aTilde = d.subsetEstimate( aHat );
YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
X.rsolve( YTilde, pvt, pvt.size() );
betaTilde =
YTilde.getColumn(0).unpivoting( pvt, k );
System.out.println( "\nThe subset estimate of coefficients = \n" +
betaTilde );
System.out.println( "Quadratic loss = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaTilde)) )))
.getColumn(0).sum2() );
aTilde = d.empiricalBayesEstimate( aHat );
YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
X.rsolve( YTilde, pvt, pvt.size() );
betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
System.out.println( "\nThe empirical Bayes estimate of coefficients = \n"+
betaTilde );
System.out.println( "Quadratic loss = " +
( new PaceMatrix( X.times( new
PaceMatrix(beta.minus(betaTilde)) )))
.getColumn(0).sum2() );
}
}