From 16ee5688d77afbd576525f5b87453dd1543bba94 Mon Sep 17 00:00:00 2001 From: Abratiychuk Date: Fri, 4 Jun 2010 14:29:26 +0300 Subject: [PATCH] Added SparseVector implementation and Unit tests for it --- src/Numerics/Constants.cs | 5 + .../LinearAlgebra/Double/SparseVector.cs | 1235 +++++++++++++++++ src/Numerics/Numerics.csproj | 1 + src/Numerics/Version.cs | 8 +- .../Double/SparseVectorTest.TextHandling.cs | 132 ++ .../Double/SparseVectorTest.cs | 369 +++++ src/UnitTests/UnitTests.csproj | 3 + 7 files changed, 1750 insertions(+), 3 deletions(-) create mode 100644 src/Numerics/LinearAlgebra/Double/SparseVector.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.TextHandling.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.cs diff --git a/src/Numerics/Constants.cs b/src/Numerics/Constants.cs index 67504466..6352bbab 100644 --- a/src/Numerics/Constants.cs +++ b/src/Numerics/Constants.cs @@ -164,6 +164,11 @@ namespace MathNet.Numerics /// public const int SizeOfDouble = sizeof(double); + /// + /// The size of a double in bytes. + /// + public const int SizeOfInt = sizeof(int); + /// /// The size of a float in bytes. /// diff --git a/src/Numerics/LinearAlgebra/Double/SparseVector.cs b/src/Numerics/LinearAlgebra/Double/SparseVector.cs new file mode 100644 index 00000000..fa2d00dc --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/SparseVector.cs @@ -0,0 +1,1235 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using NumberTheory; + using Properties; + using Threading; + + public class SparseVector : Vector + { + /// + /// Gets the vector's internal data. The array containing the actual values; only the non-zero values are stored. + /// + private double[] NonZeroValues = new double[0]; + + /// + /// The indices of the non-zero entries. + /// + private int[] NonZeroIndices = new int[0]; + /// + /// Returns the number of non zero elements in the vector. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get; + private set; + } + + #region Constructors + /// + /// Initializes a new instance of the class with a given size. + /// + /// + /// the size of the vector. + /// + /// + /// If is less than one. + /// + public SparseVector(int size) : base(size) { } + + /// + /// Initializes a new instance of the class with a given size + /// and each element set to the given value; + /// + /// + /// the size of the vector. + /// + /// + /// the value to set each element to. + /// + /// + /// If is less than one. + /// + public SparseVector(int size, double value) : this(size) + { + if (value == 0.0) //Skip adding values + return; + + // We already know that this vector is "full", let's allocate all needed memory + NonZeroValues = new double[size]; + NonZeroIndices = new int[size]; + NonZerosCount = size; + + CommonParallel.For( + 0, + this.Count, + index => + { + NonZeroValues[index] = value; + NonZeroIndices[index] = index; + }); + } + + /// + /// Initializes a new instance of the class by + /// copying the values from another. + /// + /// + /// The vector to create the new vector from. + /// + public SparseVector(Vector other) : this(other.Count) + { + var vector = other as SparseVector; + if (vector == null) + { + for (int i = 0; i < other.Count; i++ ) + this[i] = other[i]; + } + else + { + NonZeroValues = new double[vector.NonZerosCount]; + NonZeroIndices = new int[vector.NonZerosCount]; + NonZerosCount = vector.NonZerosCount; + + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + Buffer.BlockCopy(vector.NonZeroValues, 0, this.NonZeroValues, 0, vector.NonZerosCount * Constants.SizeOfDouble); + Buffer.BlockCopy(vector.NonZeroIndices, 0, this.NonZeroIndices, 0, vector.NonZerosCount * Constants.SizeOfInt); + } + } + + /// + /// Initializes a new instance of the class by + /// copying the values from another. + /// + /// + /// The vector to create the new vector from. + /// + public SparseVector(SparseVector other) : this(other.Count) + { + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + NonZeroValues = new double[other.NonZerosCount]; + NonZeroIndices = new int[other.NonZerosCount]; + NonZerosCount = other.NonZerosCount; + + Buffer.BlockCopy(other.NonZeroValues, 0, this.NonZeroValues, 0, other.NonZerosCount * Constants.SizeOfDouble); + Buffer.BlockCopy(other.NonZeroIndices, 0, this.NonZeroIndices, 0, other.NonZerosCount * Constants.SizeOfInt); + } + + /// + /// Initializes a new instance of the class for an array. + /// + /// The array to create this vector from. + /// The vector copy the array. Any changes to the vector will NOT change the array. + public SparseVector(double[] array) : this(array.Length) + { + for (int i = 0; i < array.Length; i++ ) + this[i] = array[i]; + } + #endregion + + /// + /// Create a matrix based on this vector in column form (one single column). + /// + /// This vector as a column matrix. + public override Matrix ToColumnMatrix() + { + throw new NotImplementedException(); + //var matrix = new SparseMatrix(this.Count, 1); + //CommonParallel.For( + // 0, + // this.Count, + // index => matrix[i, 0] = vector[index]); + //return matrix; + } + + /// + /// Create a matrix based on this vector in row form (one single row). + /// + /// This vector as a row matrix. + public override Matrix ToRowMatrix() + { + throw new NotImplementedException(); + //var matrix = new SparseMatrix(1, this.Count); + //CommonParallel.For( + // 0, + // this.Count, + // index => matrix[0, i] = vector[index]); + //return matrix; + } + + private readonly object lockObject = new object(); + /// Gets or sets the value at the given . + /// The index of the value to get or set. + /// The value of the vector at the given . + /// If is negative or + /// greater than the size of the vector. + public override double this[int index] + { + get + { + // If index is out of bounds + if ((index < 0) || (index >= Count)) + { + throw new IndexOutOfRangeException(); + } + + lock (lockObject) + { + // Search if item idex exists in NonZeroIndices array in range "0 - real nonzero values count" + int itemIndex = Array.BinarySearch(NonZeroIndices, 0, NonZerosCount, index); + if (itemIndex >= 0) + return NonZeroValues[itemIndex]; + } + return 0.0; + } + set + { + // If index is out of bounds + if ((index < 0) || (index >= Count)) + { + throw new IndexOutOfRangeException(); + } + + lock (lockObject) + { + SetValue(index, value); + } + } + } + + /// + /// Creates a matrix with the given dimensions using the same storage type + /// as this vector. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// + /// A matrix with the given dimensions. + /// + public override Matrix CreateMatrix(int rows, int columns) + { + throw new NotImplementedException(); + //return new SparseMatrix(rows, columns); + } + + /// + /// Creates a Vector of the given size using the same storage type + /// as this vector. + /// + /// + /// The size of the Vector to create. + /// + /// + /// The new Vector. + /// + public override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + /// + /// Copies the values of this vector into the target vector. + /// + /// + /// The vector to copy elements into. + /// + /// + /// If is . + /// + /// + /// If is not the same size as this vector. + /// + public override void CopyTo(Vector target) + { + if (target == null) + { + throw new ArgumentNullException("target"); + } + + if (this.Count != target.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "target"); + } + + if (ReferenceEquals(this, target)) + { + return; + } + + var otherVector = target as SparseVector; + if (otherVector == null) + { + CommonParallel.For( + 0, + this.Count, + index => target[index] = this[index]); + } + else + { + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + otherVector.NonZeroValues = new double[this.NonZerosCount]; + otherVector.NonZeroIndices = new int[this.NonZerosCount]; + otherVector.NonZerosCount = this.NonZerosCount; + + Buffer.BlockCopy(this.NonZeroValues, 0, otherVector.NonZeroValues, 0, this.NonZerosCount * Constants.SizeOfDouble); + Buffer.BlockCopy(this.NonZeroIndices, 0, otherVector.NonZeroIndices, 0, this.NonZerosCount * Constants.SizeOfInt); + } + } + + #region Operators and supplementary functions + + // NOTE: There are no operators as: + // public static implicit operator SparseVector(double[] array) + // and + // public static implicit operator double[](SparseVector vector) + // as it is in DenseVector. Because when creating vector from double[] values are copied to internal storage and if user wants + // to get double[] he should call SparseVector.ToArray(), then double[] will be generated and returned to a user.\ + + // In DenseVector implementation reference to double[] is assigned to interanl storage when casting from double[] and returned + // when casting to double[] + + /// + /// Adds a scalar to each element of the vector. + /// + /// The scalar to add. + public override void Add(double scalar) + { + if (scalar == 0.0) + { + return; + } + for (int i = 0; i < this.Count; i++ ) + this[i] += scalar; + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + /// If the result vector is . + /// If this vector and are not the same size. + public override void Add(double scalar, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (this.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + this.CopyTo(result); + result.Add(scalar); + } + + /// + /// Adds another vector to this vector. + /// + /// The vector to add to this one. + /// If the other vector is . + /// If this vector and are not the same size. + public override void Add(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var sparseVector = other as SparseVector; + + if (sparseVector == null) + { + base.Add(other); + } + else + { + this.AddScaledSparceVector(1.0, sparseVector); + } + } + + private void AddScaledSparceVector(double alpha, SparseVector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (alpha == 0.0) + { + return; + } + + // I don't use ILinearAlgebraProvider because we will get no benefit due to "lock" in this[index] + // Possible fucniton in ILinearAlgebraProvider may be AddSparseVectorToScaledSparseVector(T[] y, int[] yIndices, T alpha, T[] x, int[] xIndices); + // But it require to develop value setting algorithm and due to "lock" it will be even more greedy then implemented below + if (ReferenceEquals(this, other)) + { + // Adding the same instance of sparse vector. That means if we modify "this" then "other" will be modified too. + // To avoid such problem lets change values in internal storage of "this" + if (alpha == 1.0) + { + for (int i = 0; i < this.NonZerosCount; i++) + { + this.NonZeroValues[i] += this.NonZeroValues[i]; + } + } + else if (alpha == -1.0) + { + NonZerosCount = 0; // Vector is subtracted from itself + } + else + { + for (int i = 0; i < this.NonZerosCount; i++) + { + this[other.NonZeroIndices[i]] += alpha * this.NonZeroValues[i]; + } + } + } + else + { + // "this" and "other" are different objects, so by modifying "this" the "other" object will not be changed + if (alpha == 1.0) + { + for (int i = 0; i < other.NonZerosCount; i++) + { + this[other.NonZeroIndices[i]] += other.NonZeroValues[i]; + } + } + else + { + for (int i = 0; i < other.NonZerosCount; i++) + { + this[other.NonZeroIndices[i]] += alpha * other.NonZeroValues[i]; + } + } + } + } + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void Add(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (this.Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (this.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = result.CreateVector(result.Count); + Add(other, tmp); + tmp.CopyTo(result); + } + else + { + this.CopyTo(result); + result.Add(other); + } + } + + /// + /// Returns a Vector containing the same values of rightSide. + /// + /// This method is included for completeness. + /// The vector to get the values from. + /// A vector containing a the same values as . + /// If is . + public static Vector operator +(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return rightSide.Plus(); + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static Vector operator +(SparseVector leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + var ret = leftSide.Clone(); + ret.Add(rightSide); + return ret; + } + /// + /// Subtracts a scalar from each element of the vector. + /// + /// The scalar to subtract. + public override void Subtract(double scalar) + { + if (scalar == 0.0) + { + return; + } + for (int i = 0; i < this.Count; i++) + this[i] -= scalar; + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + /// If the result vector is . + /// If this vector and are not the same size. + public override void Subtract(double scalar, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (this.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + this.CopyTo(result); + result.Subtract(scalar); + } + + /// + /// Subtracts another vector from this vector. + /// + /// The vector to subtract from this one. + /// If the other vector is . + /// If this vector and are not the same size. + public override void Subtract(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var sparseVector = other as SparseVector; + + if (sparseVector == null) + { + base.Subtract(other); + } + else + { + this.AddScaledSparceVector(-1.0, sparseVector); + } + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void Subtract(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (this.Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (this.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = result.CreateVector(result.Count); + Subtract(other, tmp); + tmp.CopyTo(result); + } + else + { + this.CopyTo(result); + result.Subtract(other); + } + } + + /// + /// Returns a Vector containing the negated values of rightSide. + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static Vector operator -(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static Vector operator -(SparseVector leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + var ret = leftSide.Clone(); + ret.Subtract(rightSide); + return ret; + } + + /// + /// Returns a negated vector. + /// + /// The negated vector. + /// Added as an alternative to the unary negation operator. + public override Vector Negate() + { + var result = new SparseVector(this.Count) + { + NonZeroValues = new double[this.NonZerosCount], + NonZeroIndices = new int[this.NonZerosCount], + NonZerosCount = this.NonZerosCount + }; + + Buffer.BlockCopy(this.NonZeroIndices, 0, result.NonZeroIndices, 0, this.NonZerosCount * Constants.SizeOfInt); + + CommonParallel.For( + 0, + this.NonZerosCount, + index => result.NonZeroValues[index] = -this.NonZeroValues[index]); + + return result; + } + + /// + /// Multiplies a scalar to each element of the vector. + /// + /// The scalar to multiply. + public override void Multiply(double scalar) + { + if (scalar == 1.0) + { + return; + } + if (scalar == 0) + { + NonZerosCount = 0; // Set array empty + } + Control.LinearAlgebraProvider.ScaleArray(scalar, this.NonZeroValues); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector to add. + /// The result of the addition. + /// If is not of the same size. + /// If is . + public override double DotProduct(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + double result = 0; + + // base implementation iterates though all elements, but we need only take non-zeros + for (var i = 0; i < this.NonZerosCount; i++) + { + result += this.NonZeroValues[i] * other[this.NonZeroIndices[i]]; + } + return result; + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The vector to scale. + /// The scalar value. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(SparseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + var ret = (SparseVector)leftSide.Clone(); + ret.Multiply(rightSide); + return ret; + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The scalar value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(double leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + var ret = (SparseVector)rightSide.Clone(); + ret.Multiply(leftSide); + return ret; + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static double operator *(SparseVector leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a scalar. + /// + /// The vector to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static SparseVector operator /(SparseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + var ret = (SparseVector)leftSide.Clone(); + ret.Multiply(1.0 / rightSide); + return ret; + } + #endregion + + #region Vector Norms + + /// + /// Euclidean Norm also known as 2-Norm. + /// + /// Scalar ret = sqrt(sum(this[i]^2)) + public override double Norm() + { + var sum = 0.0; + + for (var i = 0; i < this.Count; i++) + { + sum = SpecialFunctions.Hypotenuse(sum, this[i]); + } + + return sum; + } + + /// + /// 1-Norm also known as Manhattan Norm or Taxicab Norm. + /// + /// Scalar ret = sum(abs(this[i])) + public override double Norm1() + { + return CommonParallel.Aggregate( + 0, + this.NonZerosCount, + index => Math.Abs(this.NonZeroValues[index])); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = (sum(abs(this[i])^p))^(1/p) + public override double NormP(int p) + { + if (1 > p) + { + throw new ArgumentOutOfRangeException("p"); + } + + if (1 == p) + { + return this.Norm1(); + } + + if (2 == p) + { + return this.Norm(); + } + + var sum = CommonParallel.Aggregate( + 0, + this.NonZerosCount, + index => Math.Pow(Math.Abs(this.NonZeroValues[index]), p)); + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Infinity Norm. + /// + /// Scalar ret = max(abs(this[i])) + public override double NormInfinity() + { + return CommonParallel.Select( + 0, + this.NonZerosCount, + (index, localData) => localData = Math.Max(localData, Math.Abs(this.NonZeroValues[index])), Math.Max); + } + #endregion + + #region Parse Functions + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a double. + /// + /// + /// A double sparce vector containing the values specified by the given string. + /// + /// + /// The string to parse. + /// + public static SparseVector Parse(string value) + { + return Parse(value, null); + } + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a double. + /// + /// + /// A double sparce vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static SparseVector Parse(string value, IFormatProvider formatProvider) + { + if (value == null) + { + throw new ArgumentNullException(value); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // keywords + var textInfo = formatProvider.GetTextInfo(); + var keywords = new[] { textInfo.ListSeparator }; + + // lexing + var tokens = new LinkedList(); + GlobalizationHelper.Tokenize(tokens.AddFirst(value), keywords, 0); + var token = tokens.First; + + if (token == null || tokens.Count.IsEven()) + { + throw new FormatException(); + } + + // parsing + var data = new double[(tokens.Count + 1) >> 1]; + for (var i = 0; i < data.Length; i++) + { + if (token == null || token.Value == textInfo.ListSeparator) + { + throw new FormatException(); + } + + data[i] = Double.Parse(token.Value, NumberStyles.Any, formatProvider); + + token = token.Next; + if (token != null) + { + token = token.Next; + } + } + + return new SparseVector(data); + } + + /// + /// Converts the string representation of a real sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out SparseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a real sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out SparseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion + + #region Implementation + /// + /// Delete, Add or Update the value in NonZeroValues and NonZeroIndices + /// + /// Value real index in array + /// Value + /// This method assume that index is between 0 and Array Size + private void SetValue(int index, double value) + { + // Search if "index" already exists in range "0 - real nonzero values count" + int itemIndex = Array.BinarySearch(NonZeroIndices, 0, NonZerosCount, index); + + if (itemIndex >= 0) + { + // Item already exist at itemIndex + if (value == 0.0) + { + // Value is zero. Let's delete it from Values and Indices array + for (int i = itemIndex + 1; i < NonZerosCount; i++) + { + NonZeroValues[i - 1] = NonZeroValues[i]; + NonZeroIndices[i - 1] = NonZeroIndices[i]; + } + NonZerosCount -= 1; + + // Check if the storage needs to be shrink. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((NonZerosCount > 1024) && (NonZerosCount < NonZeroIndices.Length / 2)) + { + Array.Resize(ref NonZeroValues, NonZerosCount); + Array.Resize(ref NonZeroIndices, NonZerosCount); + } + + } + else + { + NonZeroValues[itemIndex] = value; + } + } + else + { + itemIndex = ~itemIndex; //Index where to put new value + + // Check if the storage needs to be increased + if (NonZerosCount == NonZeroValues.Length) + { + // Value and Indices arrays are completely full so we increase the size + int size = Math.Min(NonZeroValues.Length + GrowthSize(), Count); + Array.Resize(ref NonZeroValues, size); + Array.Resize(ref NonZeroIndices, size); + } + + // Move all values (with an position larger than index) in the value array + // to the next position + // move all values (with an position larger than index) in the columIndices + // array to the next position + for (int i = NonZerosCount - 1; i > itemIndex - 1; i--) + { + NonZeroValues[i + 1] = NonZeroValues[i]; + NonZeroIndices[i + 1] = NonZeroIndices[i]; + } + + // Add the value and the column index + NonZeroValues[itemIndex] = value; + NonZeroIndices[itemIndex] = index; + + // increase the number of non-zero numbers by one + NonZerosCount += 1; + } + } + /// + /// Calculates the amount with which to grow the storage array's if they need to be + /// increased in size. + /// + private int GrowthSize() + { + int delta; + if (NonZeroValues.Length > 1024) + { + delta = NonZeroValues.Length / 4; + } + else + { + if (NonZeroValues.Length > 256) + { + delta = 512; + } + else + { + delta = NonZeroValues.Length > 64 ? 128 : 32; + } + } + return delta; + } + + #endregion + + #region System.Object override + /// + /// Check equality. If this is regular vector, then chek by base implementation. If Sparse - use own equition + /// + /// Object to compare + /// + public override bool Equals(object obj) + { + var sparseVector = obj as SparseVector; + + if (sparseVector == null) + return base.Equals(obj); + + // Accept if the argument is the same object as this. + if (ReferenceEquals(this, sparseVector)) + { + return true; + } + + if ((this.Count != sparseVector.Count) || (this.NonZerosCount != sparseVector.NonZerosCount)) + { + return false; + } + + // If all else fails, perform element wise comparison. + for (var index = 0; index < this.NonZerosCount; index++) + { + if (!this.NonZeroValues[index].AlmostEqual(sparseVector.NonZeroValues[index]) || (this.NonZeroIndices[index] != sparseVector.NonZeroIndices[index])) + { + return false; + } + } + return true; + } + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + var hashNum = Math.Min(this.NonZerosCount, 20); + long hash = 0; + for (var i = 0; i < hashNum; i++) + { +#if SILVERLIGHT + hash ^= Precision.DoubleToInt64Bits(this.NonZeroValues[i]); +#else + hash ^= BitConverter.DoubleToInt64Bits(this.NonZeroValues[i]); +#endif + } + + return BitConverter.ToInt32(BitConverter.GetBytes(hash), 4); + } +#endregion + } +} diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 2c45494a..ccac2022 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -151,6 +151,7 @@ + diff --git a/src/Numerics/Version.cs b/src/Numerics/Version.cs index 88bdb892..018e917a 100644 --- a/src/Numerics/Version.cs +++ b/src/Numerics/Version.cs @@ -22,9 +22,11 @@ // OTHER DEALINGS IN THE SOFTWARE. // /* This file is automatically generated - do not modify it. Change Version.tt instead. - Last generated on: 15/05/2010 20:53:09 + Last generated on: 04.06.2010 14:16:56 */ + using System.Reflection; -[assembly: AssemblyVersion("2010.05.15.1253")] -[assembly: AssemblyFileVersion("2010.05.15.1253")] \ No newline at end of file + +[assembly: AssemblyVersion("2010.06.4.856")] +[assembly: AssemblyFileVersion("2010.06.4.856")] \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.TextHandling.cs b/src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.TextHandling.cs new file mode 100644 index 00000000..09536b83 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.TextHandling.cs @@ -0,0 +1,132 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// 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. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double +{ + using System; + using System.Globalization; + using LinearAlgebra.Double; + using MbUnit.Framework; + + public class SparseVectorTextHandlingTest + { + [Test] + [Row("2", "2")] + [Row("(3)", "3")] + [Row("[1,2,3]", "1,2,3")] + [Row(" [ 1 , 2 , 3 ] ", "1,2,3")] + [Row(" [ -1 , 2 , +3 ] ", "-1,2,3")] + [Row(" [1.2,3.4 , 5.6] ", "1.2,3.4,5.6")] + public void CanParseDoubleSparseVectorsWithInvariant(string stringToParse, string expectedToString) + { + var formatProvider = CultureInfo.InvariantCulture; + SparseVector vector = SparseVector.Parse(stringToParse, formatProvider); + + Assert.AreEqual(expectedToString, vector.ToString(formatProvider)); + } + + [Test] + [Row(" 1.2,3.4 , 5.6 ", "1.2,3.4,5.6", "en-US")] + [Row(" 1.2;3.4 ; 5.6 ", "1.2;3.4;5.6", "de-CH")] + [Row(" 1,2;3,4 ; 5,6 ", "1,2;3,4;5,6", "de-DE")] + public void CanParseDoubleSparseVectorsWithCulture(string stringToParse, string expectedToString, string culture) + { + var formatProvider = CultureInfo.GetCultureInfo(culture); + SparseVector vector = SparseVector.Parse(stringToParse, formatProvider); + + Assert.AreEqual(expectedToString, vector.ToString(formatProvider)); + } + + [Test] + [Row("15")] + [Row("1{0}2{1}3{0}4{1}5{0}6")] + public void CanParseDoubleSparseVectors(string vectorAsString) + { + var mappedString = String.Format( + vectorAsString, + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, + CultureInfo.CurrentCulture.TextInfo.ListSeparator); + + SparseVector vector = SparseVector.Parse(mappedString); + + Assert.AreEqual(mappedString, vector.ToString()); + } + + [Test] + [MultipleAsserts] + public void ParseThrowsFormatExceptionIfMissingClosingParen() + { + Assert.Throws(() => SparseVector.Parse("(1")); + Assert.Throws(() => SparseVector.Parse("[1")); + } + + [Test] + public void CanTryParseDoubleSparseVector() + { + var data = new[] { 1.2, 3.4, 5.6e-78 }; + var text = String.Format( + "{1}{0}{2}{0}{3}", + CultureInfo.CurrentCulture.TextInfo.ListSeparator, + data[0], + data[1], + data[2]); + + SparseVector vector; + var ret = SparseVector.TryParse(text, out vector); + Assert.IsTrue(ret); + AssertHelpers.AlmostEqualList(data, vector.ToArray(), 1e-15); + + ret = SparseVector.TryParse(text, CultureInfo.CurrentCulture, out vector); + Assert.IsTrue(ret); + AssertHelpers.AlmostEqualList(data, vector.ToArray(), 1e-15); + } + + [Test] + [Row(null)] + [Row("")] + [Row(",")] + [Row("1,")] + [Row(",1")] + [Row("1,2,")] + [Row(",1,2,")] + [Row("1,,2,,3")] + [Row("1e+")] + [Row("1e")] + [Row("()")] + [Row("[ ]")] + public void TryParseReturnsFalseWhenGivenBadValueWithInvariant(string str) + { + SparseVector vector; + var ret = SparseVector.TryParse(str, CultureInfo.InvariantCulture, out vector); + Assert.IsFalse(ret); + Assert.IsNull(vector); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.cs b/src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.cs new file mode 100644 index 00000000..75a96424 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/SparseVectorTest.cs @@ -0,0 +1,369 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// 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. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double +{ + using System; + using System.Collections.Generic; + using MbUnit.Framework; + using LinearAlgebra.Double; + + public class SparseVectorTest : VectorTests + { + protected override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + protected override Vector CreateVector(IList data) + { + var vector = new SparseVector(data.Count); + for (var index = 0; index < data.Count; index++) + { + vector[index] = data[index]; + } + return vector; + } + + #region Test similar to DenseVector + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromArray() + { + var data = new double[_data.Length]; + System.Array.Copy(_data, data, _data.Length); + var vector = new SparseVector(data); + + //Assert.AreSame(data, vector.ToArray()); There is no way to cast SparseVector to double[], so "vector.ToArray()" and "array" have different references + for (var i = 0; i < data.Length; i++) + { + Assert.AreEqual(data[i], vector[i]); + } + + // vector and data are different instances actually + //vector[0] = 100.0; + //Assert.AreEqual(100.0, data[0]); + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromAnotherSparseVector() + { + var vector = new SparseVector(_data); + var other = new SparseVector(vector); + + + Assert.AreNotSame(vector, other); + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromAnotherVector() + { + var vector = (Vector)new SparseVector(_data); + var other = new SparseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromUserDefinedVector() + { + var vector = new UserDefinedVector(_data); + var other = new SparseVector(vector); + + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(vector[i], other[i]); + } + } + + [Test] + public void CanCreateSparseVectorWithConstantValues() + { + var vector = new SparseVector(5, 5); + Assert.ForAll(vector, value => value == 5); + } + + // TODO: Remove [Ignore] when SparseMatrix developed + [Test] + [MultipleAsserts] + [Ignore] + public void CanCreateSparseMatrix() + { + var vector = new SparseVector(3); + var matrix = vector.CreateMatrix(2, 3); + Assert.AreEqual(2, matrix.RowCount); + Assert.AreEqual(3, matrix.ColumnCount); + } + + + [Test] + [MultipleAsserts] + public void CanConvertSparseVectorToArray() + { + var vector = new SparseVector(_data); + var array = vector.ToArray(); + Assert.IsInstanceOfType(typeof(double[]), array); + //Assert.AreSame(vector.ToArray(), array); There is no way to cast SparseVector to double[], so "vector.ToArray()" and "array" have different references + Assert.AreElementsEqual(vector, array); + } + + [Test] + [MultipleAsserts] + public void CanConvertArrayToSparseVector() + { + var array = new[] { 0.0, 1.0, 2.0, 3.0, 4.0 }; + var vector = new SparseVector(array); + Assert.IsInstanceOfType(typeof(SparseVector), vector); + Assert.AreElementsEqual(array, array); + } + + [Test] + public void CanCallUnaryPlusOperatorOnSparseVector() + { + var vector = new SparseVector(_data); + var other = +vector; + Assert.AreSame(vector, other, "Should be the same vector"); + } + + [Test] + [MultipleAsserts] + public void CanAddTwoSparseVectorsUsingOperator() + { + var vector = new SparseVector(_data); + var other = new SparseVector(_data); + var result = vector + other; + + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i], vector[i], "Making sure the original vector wasn't modified."); + Assert.AreEqual(_data[i], other[i], "Making sure the original vector wasn't modified."); + Assert.AreEqual(_data[i] * 2.0, result[i]); + } + } + + [Test] + public void CanCallUnaryNegationOperatorOnDenseVector() + { + var vector = new SparseVector(_data); + var other = -vector; + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(-_data[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractTwoSparseVectorsUsingOperator() + { + var vector = new SparseVector(_data); + var other = new SparseVector(_data); + var result = vector - other; + + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i], vector[i], "Making sure the original vector wasn't modified."); + Assert.AreEqual(_data[i], other[i], "Making sure the original vector wasn't modified."); + Assert.AreEqual(0.0, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanMultiplySparseVectorByScalarUsingOperators() + { + var vector = new SparseVector(_data); + vector = vector * 2.0; + + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i] * 2.0, vector[i]); + } + + vector = vector * 1.0; + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i] * 2.0, vector[i]); + } + + vector = new SparseVector(_data); + vector = 2.0 * vector; + + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i] * 2.0, vector[i]); + } + + vector = 1.0 * vector; + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i] * 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanDivideSparseVectorByScalarUsingOperators() + { + var vector = new SparseVector(_data); + vector = vector / 2.0; + + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i] / 2.0, vector[i]); + } + + vector = vector / 1.0; + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(_data[i] / 2.0, vector[i]); + } + } + #endregion + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromDenseVector() + { + var vector = (Vector)new DenseVector(_data); + var other = new SparseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < _data.Length; i++) + { + Assert.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CheckSparseMechanismBySettingValues() + { + var vector = new SparseVector(10000); + + //Add non-zero elements + vector[200] = 1.5; + Assert.AreEqual(1.5, vector[200]); + Assert.AreEqual(1, vector.NonZerosCount); + + vector[500] = 3.5; + Assert.AreEqual(3.5, vector[500]); + Assert.AreEqual(2, vector.NonZerosCount); + + vector[800] = 5.5; + Assert.AreEqual(5.5, vector[800]); + Assert.AreEqual(3, vector.NonZerosCount); + + vector[0] = 7.5; + Assert.AreEqual(7.5, vector[0]); + Assert.AreEqual(4, vector.NonZerosCount); + + //Remove non-zero elements + vector[200] = 0; + Assert.AreEqual(0, vector[200]); + Assert.AreEqual(3, vector.NonZerosCount); + + vector[500] = 0; + Assert.AreEqual(0, vector[500]); + Assert.AreEqual(2, vector.NonZerosCount); + + vector[800] = 0; + Assert.AreEqual(0, vector[800]); + Assert.AreEqual(1, vector.NonZerosCount); + + vector[0] = 0; + Assert.AreEqual(0, vector[0]); + Assert.AreEqual(0, vector.NonZerosCount); + } + + [Test] + [MultipleAsserts] + public void CheckSparseMechanismByZeroMultiply() + { + var vector = new SparseVector(10000); + + //Add non-zero elements + vector[200] = 1.5; + vector[500] = 3.5; + vector[800] = 5.5; + vector[0] = 7.5; + + //Multiply by 0 + vector *= 0; + Assert.AreEqual(0, vector[200]); + Assert.AreEqual(0, vector[500]); + Assert.AreEqual(0, vector[800]); + Assert.AreEqual(0, vector[0]); + Assert.AreEqual(0, vector.NonZerosCount); + } + + + [Test] + public void CanDotProductOfTwoSparseVectors() + { + var vectorA = new SparseVector(10000); + vectorA[200] = 1; + vectorA[500] = 3; + vectorA[800] = 5; + vectorA[100] = 7; + vectorA[900] = 9; + + var vectorB = new SparseVector(10000); + vectorB[300] = 3; + vectorB[500] = 5; + vectorB[800] = 7; + + + Assert.AreEqual(50.0, vectorA.DotProduct(vectorB)); + } + + [Test] + public void CreateHugeSparseVector() + { + var data = new double[1000000]; + var rnd = new Random(); + for (int i = 0; i < 1000000; i++) + data[i] = rnd.Next(); + + var vector = new SparseVector(data); + } + } +} diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 8b4f06e8..5a65d53a 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -44,6 +44,7 @@ prompt 4 AllRules.ruleset + AnyCPU pdbonly @@ -120,6 +121,8 @@ + +