/* * Copyright (c) 2014 Oculus Info Inc. * http://www.oculusinfo.com/ * * Released under the MIT License. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.oculusinfo.math.linearalgebra; import java.io.Serializable; import java.util.Arrays; import java.util.List; /** * A basic vector class with which to do linear algebra * * @author nkronenfeld */ public class Vector implements Serializable { private static final long serialVersionUID = 7020657491745842344L; public static Vector zeroVector (int size) { double[] coords = new double[size]; for (int i=0; i<size; ++i) coords[i] = 0.0; return new Vector(coords); } private double _epsilon; private double[] _data; public Vector (double... data) { _data = data; _epsilon = 1E-12; } /** * Construct a vector from a list. * Note that this will throw an exception if there is a null in the list. */ public Vector (List<Double> data) { _data = new double[data.size()]; for (int i=0; i<_data.length; ++i) _data[i] = data.get(i); } public Vector (Vector toCopy) { _data = Arrays.copyOf(toCopy._data, toCopy._data.length); _epsilon = toCopy._epsilon; } /** * Set the precision for equality and zero tests for this vector. If any * calculation yields two numbers closer than the given precision, they are * deemed equal. If any calculation yields a number whose absolute value is * less than the given precision, it is deemed zero. * * @param precision The precision for calculations with this vector. */ public void setPrecision (double precision) { _epsilon = precision; } /** * Gets the precision used by this vector for equality and zero tests. See * {@link #setPrecision(double)} */ public double getPrecision () { return _epsilon; } // //////////////////////////////////////////////////////////////////////// // Section: Clusterable implementation // /** * Get the simple cartesian distance between this and another vector. */ public double getDistance (Vector other) { return Math.sqrt(getDistanceSquared(other)); } /** * Get the square of the simple cartesian distance between this and another * vector. */ public double getDistanceSquared (Vector other) { return subtract(other).vectorLengthSquared(); } /** * Get the cartesian length of this vector. */ public double vectorLength () { return Math.sqrt(vectorLengthSquared()); } /** * Get the square of the cartesian length of this vector. */ public double vectorLengthSquared () { return this.dot(this); } /** * Get the coordinate-wise mean of the given vectors. */ static public Vector mean (List<? extends Vector> data) { if (data.isEmpty()) throw new IllegalArgumentException("Attempt to take the mean of 0 vectors"); Vector mean = zeroVector(data.get(0).size()); for (Vector datum: data) { mean = mean.add(datum); } mean = mean.scale(1.0/data.size()); return mean; } /** * Get the number of dimensions of this vector (i.e., its size, or length). */ public int size () { return _data.length; } /** * Get the nth coordinate of this vector. * * @param index n * @return The value of the nth coordinate. */ public double coord (int index) { if (index < 0) throw new IndexOutOfBoundsException("Illegal index 0"); if (index >= _data.length) throw new IndexOutOfBoundsException("Illegal index "+index+" on vector of length "+_data.length); return _data[index]; } /** * Calculate the sum of this and another vector. Both vectors must have the * same number of dimensions. Neither input vector is altered. * * @param that The other vector * @return The sum of the two vectors. */ public Vector add (Vector that) { int len = size(); if (that.size() != len) throw new IllegalArgumentException("Attempt to add vectors of different lengths"); double[] result = new double[len]; for (int i=0; i<len; ++i) { result[i] = _data[i] + that._data[i]; } return new Vector(result); } /** * Calculate the difference of this and another vector. Both vectors must * have the same number of dimensions. Neither input vector is altered. * * @param that The other vector * @return The difference between the two vectors (this - that) */ public Vector subtract (Vector that) { int len = size(); if (that.size() != len) throw new IllegalArgumentException("Attempt to subtract vectors of different lengths"); double[] result = new double[len]; for (int i=0; i<len; ++i) { result[i] = _data[i] - that._data[i]; } return new Vector(result); } /** * Calculate a scaled version of this vector. * * @param scale The scale to apply. * @return A new vector whose value is a scaled version of this vector. */ public Vector scale (double scale) { int len = size(); double[] coords = new double[len]; for (int i=0; i<len; ++i) coords[i] = _data[i]*scale; return new Vector(coords); } /** * Calculate the dot product of two vectors. Both vectors must have the same * number of dimensions. Neither input vector is altered. * * @param that The other vector to multiply by this one * @return The dot product of this and that */ public double dot (Vector that) { int len = size(); if (that.size() != len) throw new IllegalArgumentException("Attempt to take the dot product of vectors of different lengths"); double res = 0.0; for (int i=0; i<size(); ++i) { res += _data[i]*that._data[i]; } return res; } /** * Calculate the cross product of two vectors. Both vectors must be of 3 components. * @param v cross product operand * @return The cross product */ public Vector cross (Vector v) { if (3 != size() || 3 != v.size()) throw new IllegalArgumentException("Attempt to take the cross product of non-3-vectors"); double xa = coord(0); double ya = coord(1); double za = coord(2); double xb = v.coord(0); double yb = v.coord(1); double zb = v.coord(2); return new Vector(ya*zb-za*yb, za*xb-xa*zb, xa*yb-ya*xb); } @Override public int hashCode () { int len = size(); int hash = len; for (int i=0; i<len; ++i) { hash = hash*71+(int) Math.round(_data[i]*73); } return hash; } @Override public boolean equals (Object obj) { if (this == obj) return true; if (null == obj) return false; if (!(obj instanceof Vector)) return false; Vector v = (Vector) obj; int len = size(); if (v.size() != len) return false; for (int i=0; i<len; ++i) { double ours = _data[i]; double theirs = v._data[i]; if (Double.isNaN(ours) || Double.isNaN(theirs)) { if (!(Double.isNaN(ours) && Double.isNaN(theirs))) return false; } else if (Math.abs(ours-theirs)>=_epsilon) return false; } return true; } @Override public String toString () { StringBuffer res = new StringBuffer(); res.append("["); for (int i=0; i<_data.length; ++i) { if (i>0) res.append(", "); res.append(String.format("%.4f", _data[i])); } res.append("]"); return res.toString(); } }