package de.lmu.ifi.dbs.elki.math.linearalgebra;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
Copyright (C) 2013
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import java.util.Arrays;
import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.utilities.FormatUtil;
/**
* Provides a vector object that encapsulates an m x 1 - matrix object.
*
* @author Elke Achtert
*
* @apiviz.landmark
*/
public class Vector implements NumberVector {
/**
* Array for internal storage of elements.
*
* @serial internal array storage.
*/
protected final double[] elements;
/**
* Error message (in assertions!) when vector dimensionalities do not agree.
*/
public static final String ERR_VEC_DIMENSIONS = "Vector dimensions do not agree.";
/**
* Error message (in assertions!) when matrix dimensionalities do not agree.
*/
public static final String ERR_MATRIX_INNERDIM = "Matrix inner dimensions do not agree.";
/**
* Error message (in assertions!) when dimensionalities do not agree.
*/
private static final String ERR_DIMENSIONS = "Dimensionalities do not agree.";
/**
* Construct a vector from a given array.
*
* @param values array of doubles
*/
public Vector(final double... values) {
elements = values;
}
/**
* Provides an m x 1 vector.
*
* @param m the number of rows
*/
public Vector(final int m) {
elements = new double[m];
}
/**
* Returns a randomly created vector of length 1.0.
*
* @param dimensionality dimensionality
* @return the dimensionality of the vector
*/
public static final Vector randomNormalizedVector(final int dimensionality) {
final Vector v = new Vector(dimensionality);
double norm = 0;
while (norm <= 0) {
for (int i = 0; i < dimensionality; i++) {
v.elements[i] = Math.random();
}
norm = v.euclideanLength();
}
for (int row = 0; row < dimensionality; row++) {
v.elements[row] /= norm;
}
return v;
}
/**
* Returns the ith unit vector of the specified dimensionality.
*
* @param dimensionality the dimensionality of the vector
* @param i the index
* @return the ith unit vector of the specified dimensionality
*/
public static final Vector unitVector(final int dimensionality, final int i) {
final Vector v = new Vector(dimensionality);
v.elements[i] = 1;
return v;
}
/**
* Returns a copy of this vector.
*
* @return a copy of this vector
*/
public final Vector copy() {
return new Vector(elements.clone());
}
@Override
public Vector clone() {
return this.copy();
}
/**
* Access the internal two-dimensional array.
*
* @return Pointer to the two-dimensional array of matrix elements.
*/
public final double[] getArrayRef() {
return elements;
}
/**
* Copy the internal two-dimensional array.
*
* @return Two-dimensional array copy of matrix elements.
*/
public final double[] getArrayCopy() {
return elements.clone();
}
/**
* Returns the dimensionality of this vector.
*
* @return the dimensionality of this vector
*/
@Override
public final int getDimensionality() {
return elements.length;
}
/**
* Returns the value at the specified row.
*
* @param i the row index
* @return the value at row i
*/
public final double get(final int i) {
return elements[i];
}
/**
* Sets the value at the specified row.
*
* @param i the row index
* @param value the value to be set
*
* @return the modified vector
*/
public final Vector set(final int i, final double value) {
elements[i] = value;
return this;
}
/**
* Returns a new vector which is the result of this vector plus the specified
* vector.
*
* @param v the vector to be added
* @return the resulting vector
*/
public final Vector plus(final Vector v) {
assert (this.elements.length == v.elements.length) : ERR_VEC_DIMENSIONS;
final Vector result = new Vector(elements.length);
for (int i = 0; i < elements.length; i++) {
result.elements[i] = elements[i] + v.elements[i];
}
return result;
}
/**
* Returns a new vector which is the result of this vector plus the specified
* vector times the given factor.
*
* @param v the vector to be added
* @param s the scalar
* @return the resulting vector
*/
public final Vector plusTimes(final Vector v, final double s) {
assert (this.elements.length == v.elements.length) : ERR_VEC_DIMENSIONS;
final Vector result = new Vector(elements.length);
for (int i = 0; i < elements.length; i++) {
result.elements[i] = elements[i] + v.elements[i] * s;
}
return result;
}
/**
* a = a + b.
*
* @param b another vector
* @return a + b in this vector
*/
public final Vector plusEquals(final Vector b) {
assert (this.elements.length == b.elements.length) : ERR_VEC_DIMENSIONS;
for (int i = 0; i < elements.length; i++) {
elements[i] += b.elements[i];
}
return this;
}
/**
* a = a + s * b.
*
* @param b another vector
* @param s Scalar
* @return a + s * b in this vector
*/
public final Vector plusTimesEquals(final Vector b, final double s) {
assert (this.elements.length == b.elements.length) : ERR_VEC_DIMENSIONS;
for (int i = 0; i < elements.length; i++) {
elements[i] += s * b.elements[i];
}
return this;
}
/**
* Add a constant value to all dimensions.
*
* @param d Value to add
* @return Modified vector
*/
public final Vector plusEquals(final double d) {
for (int i = 0; i < elements.length; i++) {
elements[i] += d;
}
return this;
}
/**
* Returns this vector minus the specified vector v.
*
* @param v the vector to be subtracted from this vector
* @return this vector minus the specified vector v
*/
public final Vector minus(final Vector v) {
final Vector sub = new Vector(elements.length);
for (int i = 0; i < elements.length; i++) {
sub.elements[i] = elements[i] - v.elements[i];
}
return sub;
}
/**
* Returns this vector minus the specified vector v times s.
*
* @param v the vector to be subtracted from this vector
* @param s the scaling factor
* @return this vector minus the specified vector v
*/
public final Vector minusTimes(final Vector v, final double s) {
final Vector sub = new Vector(elements.length);
for (int i = 0; i < elements.length; i++) {
sub.elements[i] = elements[i] - v.elements[i] * s;
}
return sub;
}
/**
* a = a - b.
*
* @param b another vector
* @return a - b in this vector
*/
public final Vector minusEquals(final Vector b) {
assert (this.elements.length == b.elements.length) : ERR_VEC_DIMENSIONS;
for (int i = 0; i < elements.length; i++) {
elements[i] -= b.elements[i];
}
return this;
}
/**
* a = a - s * b.
*
* @param b another vector
* @param s Scalar
* @return a - s * b in this vector
*/
public final Vector minusTimesEquals(final Vector b, final double s) {
assert (this.elements.length == b.elements.length) : ERR_VEC_DIMENSIONS;
for (int i = 0; i < elements.length; i++) {
elements[i] -= s * b.elements[i];
}
return this;
}
/**
* Subtract a constant value from all dimensions.
*
* @param d Value to subtract
* @return Modified vector
*/
public final Vector minusEquals(final double d) {
for (int i = 0; i < elements.length; i++) {
elements[i] -= d;
}
return this;
}
/**
* Returns a new vector which is the result of this vector multiplied by the
* specified scalar.
*
* @param s the scalar to be multiplied
* @return the resulting vector
*/
public final Vector times(final double s) {
final Vector v = new Vector(elements.length);
for (int i = 0; i < elements.length; i++) {
v.elements[i] = elements[i] * s;
}
return v;
}
/**
* Multiply a matrix by a scalar in place, A = s*A.
*
* @param s scalar
* @return replace A by s*A
*/
public final Vector timesEquals(final double s) {
for (int i = 0; i < elements.length; i++) {
elements[i] *= s;
}
return this;
}
/**
* Linear algebraic matrix multiplication, A * B.
*
* @param B another matrix
* @return Matrix product, A * B
*/
public final Matrix times(final Matrix B) {
assert (B.elements.length == 1) : ERR_MATRIX_INNERDIM;
final Matrix X = new Matrix(this.elements.length, B.columndimension);
for (int j = 0; j < B.columndimension; j++) {
for (int i = 0; i < this.elements.length; i++) {
X.elements[i][j] = elements[i] * B.elements[0][j];
}
}
return X;
}
/**
* Linear algebraic matrix multiplication, AT * B.
*
* @param B another matrix
* @return Matrix product, AT * B
*/
public final Matrix transposeTimes(final Matrix B) {
assert (B.elements.length == this.elements.length) : ERR_MATRIX_INNERDIM;
final Matrix X = new Matrix(1, B.columndimension);
for (int j = 0; j < B.columndimension; j++) {
// multiply it with each row from A
double s = 0;
for (int k = 0; k < this.elements.length; k++) {
s += this.elements[k] * B.elements[k][j];
}
X.elements[0][j] = s;
}
return X;
}
/**
* Linear algebraic matrix multiplication, aT * B * c.
*
* @param B matrix
* @param c vector on the right
* @return Matrix product, aT * B * c
*/
public final double transposeTimesTimes(final Matrix B, final Vector c) {
assert (B.elements.length == this.elements.length) : ERR_MATRIX_INNERDIM;
double sum = 0.0;
for (int j = 0; j < B.columndimension; j++) {
// multiply it with each row from A
double s = 0;
for (int k = 0; k < this.elements.length; k++) {
s += this.elements[k] * B.elements[k][j];
}
sum += s * c.elements[j];
}
return sum;
}
/**
* Linear algebraic matrix multiplication, AT * B.
*
* @param B another vector
* @return Matrix product, AT * B
*/
public final double transposeTimes(final Vector B) {
assert (B.elements.length == this.elements.length) : ERR_MATRIX_INNERDIM;
double s = 0;
for (int k = 0; k < this.elements.length; k++) {
s += this.elements[k] * B.elements[k];
}
return s;
}
/**
* Linear algebraic matrix multiplication, A * B^T.
*
* @param B another matrix
* @return Matrix product, A * B^T
*/
public final Matrix timesTranspose(final Matrix B) {
assert (B.columndimension == 1) : ERR_MATRIX_INNERDIM;
final Matrix X = new Matrix(this.elements.length, B.elements.length);
for (int j = 0; j < B.elements.length; j++) {
for (int i = 0; i < this.elements.length; i++) {
X.elements[i][j] = elements[i] * B.elements[j][0];
}
}
return X;
}
/**
* Linear algebraic matrix multiplication, A * B^T.
*
* @param B another matrix
* @return Matrix product, A * B^T
*/
public final Matrix timesTranspose(final Vector B) {
final Matrix X = new Matrix(this.elements.length, B.elements.length);
for (int j = 0; j < B.elements.length; j++) {
for (int i = 0; i < this.elements.length; i++) {
X.elements[i][j] = elements[i] * B.elements[j];
}
}
return X;
}
/**
* Returns the length of this vector.
*
* @return the length of this vector
*/
public final double euclideanLength() {
double acc = 0.0;
for (int row = 0; row < elements.length; row++) {
final double v = elements[row];
acc += v * v;
}
return Math.sqrt(acc);
}
/**
* Normalizes this vector to the length of 1.0.
*
* @return this vector
*/
public final Vector normalize() {
double norm = euclideanLength();
if (norm != 0) {
for (int row = 0; row < elements.length; row++) {
elements[row] /= norm;
}
}
return this;
}
/**
* Projects this row vector into the subspace formed by the specified matrix
* v.
*
* @param v the subspace matrix
* @return the projection of p into the subspace formed by v
*/
public final Vector projection(final Matrix v) {
assert (elements.length == v.elements.length) : ERR_DIMENSIONS;
Vector sum = new Vector(elements.length);
for (int i = 0; i < v.columndimension; i++) {
// TODO: optimize - copy less?
Vector v_i = v.getCol(i);
sum.plusTimesEquals(v_i, this.transposeTimes(v_i));
}
return sum;
}
@Override
public int hashCode() {
return Arrays.hashCode(this.elements);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Vector other = (Vector) obj;
if (this.elements.length != other.elements.length) {
return false;
}
return Arrays.equals(this.elements, other.elements);
}
/**
* Returns a string representation of this vector.
*
* @return a string representation of this vector.
*/
@Override
public final String toString() {
return FormatUtil.format(this);
}
/**
* Returns a string representation of this vector without adding extra
* whitespace.
*
* @return a string representation of this vector.
*/
public final String toStringNoWhitespace() {
return "[" + FormatUtil.format(elements, ",") + "]";
}
/**
* Reset the Vector to 0.
*/
public void setZero() {
Arrays.fill(elements, 0.0);
}
/**
* Rotate vector by 90 degrees.
*
* @return self, for operation chaining.
*/
public Vector rotate90Equals() {
assert (elements.length == 2);
double temp = elements[0];
elements[0] = elements[1];
elements[1] = -temp;
return this;
}
/**
* Cross product for 3d vectors, i.e. this x other
*
* @param other Other vector
* @return Cross product of this vector and the other vector
*/
public Vector cross3D(Vector other) {
assert (elements.length == 3 && other.elements.length == 3);
Vector out = new Vector(3);
out.elements[0] = (elements[1] * other.elements[2]) - (elements[2] * other.elements[1]);
out.elements[1] = (elements[2] * other.elements[0]) - (elements[0] * other.elements[2]);
out.elements[2] = (elements[0] * other.elements[1]) - (elements[1] * other.elements[0]);
return out;
}
// ////// NumberVector API. A bit hackish. :-(
@Override
public double getMin(int dimension) {
return elements[dimension];
}
@Override
public double getMax(int dimension) {
return elements[dimension];
}
@Override
@Deprecated
public Double getValue(int dimension) {
return Double.valueOf(elements[dimension]);
}
@Override
public double doubleValue(int dimension) {
return elements[dimension];
}
@Override
public float floatValue(int dimension) {
return (float) elements[dimension];
}
@Override
public int intValue(int dimension) {
return (int) elements[dimension];
}
@Override
public long longValue(int dimension) {
return (long) elements[dimension];
}
@Override
public short shortValue(int dimension) {
return (short) elements[dimension];
}
@Override
public byte byteValue(int dimension) {
return (byte) elements[dimension];
}
@Override
public Vector getColumnVector() {
return copy();
}
}