diff --git a/.gitignore b/.gitignore index 303ac4d..11328a7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ *.vs* */bin/* */obj/* -*/packages/* \ No newline at end of file +*/packages/* +GenericTensor.sln.DotSettings.user \ No newline at end of file diff --git a/GenericTensor.sln.DotSettings.user b/GenericTensor.sln.DotSettings.user deleted file mode 100644 index e0814be..0000000 --- a/GenericTensor.sln.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - <data><HostParameters type="LocalHostParameters" /><Argument type="AttachArgument"><DisplayName>D:\main\vs_prj\GenericTensor\GenericTensor\Sample\bin\Release\netcoreapp3.1\Sample.exe</DisplayName><ProcessId>12684</ProcessId><RuntimeType>CoreClr</RuntimeType></Argument><Info type="TimelineInfo" /><CoreOptions type="CoreOptions"><CoreTempPath IsNull="False"></CoreTempPath><RemoteEndPoint IsNull="False"></RemoteEndPoint><AdditionalEnvironmentVariables /></CoreOptions><HostOptions type="HostOptions"><HostTempPath IsNull="False"></HostTempPath></HostOptions></data> \ No newline at end of file diff --git a/GenericTensor/Core/Exceptions.cs b/GenericTensor/Core/Exceptions.cs index 17d8563..ab4d4d3 100644 --- a/GenericTensor/Core/Exceptions.cs +++ b/GenericTensor/Core/Exceptions.cs @@ -55,4 +55,13 @@ public class InvalidDeterminantException : DataException internal InvalidDeterminantException(string msg) : base(msg) {} internal InvalidDeterminantException() : base() {} } + + /// + /// Thrown when there is no decomposition for provided matrix + /// + public class ImpossibleDecomposition : DataException + { + internal ImpossibleDecomposition(string msg) : base(msg) {} + internal ImpossibleDecomposition() : base() {} + } } diff --git a/GenericTensor/Declaration.cs b/GenericTensor/Declaration.cs index 5dec790..bd98273 100644 --- a/GenericTensor/Declaration.cs +++ b/GenericTensor/Declaration.cs @@ -1,4 +1,5 @@ #region copyright + /* * MIT License * @@ -22,6 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + #endregion @@ -40,6 +42,7 @@ public sealed partial class GenTensor : ICloneable where TWrapper : struct, IOperations { #region Composition + /// /// Creates a new axis that is put backward /// and then sets all elements as children @@ -67,6 +70,7 @@ public static GenTensor Concat(GenTensor a, GenTensor< #endregion #region Constructors + /// /// Creates a tensor whose all matrices are identity matrices /// 1 is achieved with @@ -145,36 +149,31 @@ public static GenTensor CreateMatrix(int width, int height) /// (its only argument is an array of integers which are indices of the tensor) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor CreateTensor(TensorShape shape, Func operation, Threading threading = Threading.Single) - => Constructors.CreateTensor(shape, operation, threading); + public static GenTensor CreateTensor(TensorShape shape, Func operation, Threading threading = Threading.Single) => Constructors.CreateTensor(shape, operation, threading); /// /// Creates a tensor from an array /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor CreateTensor(T[] data) - => Constructors.CreateTensor(data); + public static GenTensor CreateTensor(T[] data) => Constructors.CreateTensor(data); /// /// Creates a tensor from a two-dimensional array /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor CreateTensor(T[,] data) - => Constructors.CreateTensor(data); + public static GenTensor CreateTensor(T[,] data) => Constructors.CreateTensor(data); /// /// Creates a tensor from a three-dimensional array /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor CreateTensor(T[,,] data) - => Constructors.CreateTensor(data); + public static GenTensor CreateTensor(T[,,] data) => Constructors.CreateTensor(data); /// /// Creates a tensor from an n-dimensional array /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor CreateTensor(Array data) - => Constructors.CreateTensor(data); + public static GenTensor CreateTensor(Array data) => Constructors.CreateTensor(data); #endregion @@ -186,6 +185,42 @@ public static GenTensor CreateTensor(Array data) /// public GenTensor RowEchelonFormSimple() => EchelonForm.RowEchelonFormSimple(this); + + /// + /// Decomposes a matrix into a triangular one. + /// Is of the Row Echelon Form (leading elements might be differ from ones). + /// + /// In addition, returns a permutation that algorithm performs with rows. + /// Permutation array is size of rows there are in matrix. + /// + /// Initial state of that array is: + /// 1 2 3 ... numberOfRows + /// + /// For example, algorithm swaps first and third rows then: + /// 3 2 1 ... numberOfRows + /// + /// It can be useful performing decompositions + /// + public (GenTensor, int[]) RowEchelonFormPermuteSimple() + => EchelonForm.RowEchelonFormPermuteSimple(this); + + /// + /// Decomposes a matrix into a triangular one. + /// Is of the Row Echelon Form (leading elements might be differ from ones). + /// + /// In addition, returns a permutation that algorithm performs with rows. + /// Permutation array is size of rows there are in matrix. + /// + /// Initial state of that array is: + /// 1 2 3 ... numberOfRows + /// + /// For example, algorithm swaps first and third rows then: + /// 3 2 1 ... numberOfRows + /// + /// It can be useful performing decompositions + /// + public (GenTensor, int[]) RowEchelonFormPermuteSafeDivision() + => EchelonForm.RowEchelonFormPermuteSafeDivision(this); /// /// Decomposes a matrix into a triangular one. @@ -196,7 +231,6 @@ public GenTensor RowEchelonFormSafeDivision() => EchelonForm.RowEchelonFormSafeDivision(this); - /// /// Decomposes a matrix into a triangular one. /// Is of the Row Echelon Form (leading elements are ones). @@ -215,7 +249,6 @@ public GenTensor RowEchelonFormLeadingOnesSafeDivision() => EchelonForm.RowEchelonFormLeadingOnesSafeDivision(this); - /// /// Finds the reduced echelon form of a matrix. /// @@ -228,6 +261,16 @@ public GenTensor ReducedRowEchelonFormSimple() /// public GenTensor ReducedRowEchelonFormSafeDivision() => EchelonForm.ReducedRowEchelonFormSafeDivision(this); + + /// + /// Finds the reduced echelon form of a matrix. + /// Uses safe division, i. e. perform division only when computing the final result. + /// + /// Additionally returns row permutation + /// + public (GenTensor, int[]) ReducedRowEchelonFormPermuteSafeDivision() + => EchelonForm.ReducedRowEchelonFormPermuteSafeDivision(this); + #endregion #region Determinant @@ -307,6 +350,7 @@ public void InvertMatrix() /// public void TensorMatrixInvert() => Inversion.TensorMatrixInvert(this); + #endregion #region Elementary matrix operations @@ -398,70 +442,80 @@ public static GenTensor TensorMatrixMultiply(GenTensor /// T1 + T2 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseAdd(GenTensor a, GenTensor b, Threading threading = Threading.Single) + public static GenTensor PiecewiseAdd(GenTensor a, GenTensor b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseAdd(a, b, threading); /// /// T1 - T2 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseSubtract(GenTensor a, GenTensor b, Threading threading = Threading.Single) + public static GenTensor PiecewiseSubtract(GenTensor a, GenTensor b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseSubtract(a, b, threading); /// /// T1 * T2 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseMultiply(GenTensor a, GenTensor b, Threading threading = Threading.Single) + public static GenTensor PiecewiseMultiply(GenTensor a, GenTensor b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseMultiply(a, b, threading); /// /// T1 / T2 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseDivide(GenTensor a, GenTensor b, Threading threading = Threading.Single) + public static GenTensor PiecewiseDivide(GenTensor a, GenTensor b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseDivide(a, b, threading); /// /// T1 + const /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseAdd(GenTensor a, T b, Threading threading = Threading.Single) + public static GenTensor PiecewiseAdd(GenTensor a, T b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseAdd(a, b, threading); /// /// T1 - const /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseSubtract(GenTensor a, T b, Threading threading = Threading.Single) + public static GenTensor PiecewiseSubtract(GenTensor a, T b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseSubtract(a, b, threading); /// /// const - T1 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseSubtract(T a, GenTensor b, Threading threading = Threading.Single) + public static GenTensor PiecewiseSubtract(T a, GenTensor b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseSubtract(a, b, threading); /// /// T1 * const /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseMultiply(GenTensor a, T b, Threading threading = Threading.Single) + public static GenTensor PiecewiseMultiply(GenTensor a, T b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseMultiply(a, b, threading); /// /// T1 / const /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseDivide(GenTensor a, T b, Threading threading = Threading.Single) + public static GenTensor PiecewiseDivide(GenTensor a, T b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseDivide(a, b, threading); /// /// const / T1 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static GenTensor PiecewiseDivide(T a, GenTensor b, Threading threading = Threading.Single) + public static GenTensor PiecewiseDivide(T a, GenTensor b, + Threading threading = Threading.Single) => PiecewiseArithmetics.PiecewiseDivide(a, b, threading); #endregion @@ -493,6 +547,40 @@ public GenTensor TensorMatrixPower(int power, Threading threading = #endregion + #region Decompositions + + /// + /// https://www.geeksforgeeks.org/l-u-decomposition-system-linear-equations/ + /// + /// + /// LU decomposition + /// + public (GenTensor l, GenTensor u) LuDecomposition() + => LuDecomposition.Decompose(this); + + /// + /// Find PLU decomposition: matrices P, L, U such that for original matrix A: PA = LU. + /// + /// P stands for permutation matrix, permutations are made during the Gauss elimination process + /// L stands for LIBERTY lower triangle matrix + /// U stands for upper triangle matrix + /// + /// Algorithm, given matrix A: + /// 1. Form an adjacent matrix (A|E) + /// 2. Find row echelon form of that matrix (U|L_0) and permutation of the rows + /// 3. Form permutation matrix P such that P_ij = \delta_{} + /// 4. Compute L = P * L_0^{-1} + /// + /// Results are: P, L, U + /// + /// + /// LUP decomposition of given matrix + /// + public (GenTensor p, GenTensor l, GenTensor u) PluDecomposition() + => PluDecomposition.Decompose(this); + + #endregion + #region ToString & GetHashCode /// @@ -517,7 +605,8 @@ public static GenTensor VectorCrossProduct(GenTensor a /// /// Calls VectorCrossProduct for every vector in the tensor /// - public static GenTensor TensorVectorCrossProduct(GenTensor a, GenTensor b) + public static GenTensor TensorVectorCrossProduct(GenTensor a, + GenTensor b) => VectorProduct.TensorVectorCrossProduct(a, b); /// @@ -562,6 +651,7 @@ public GenTensor Forward() #endregion #region Serialization + /* * Serialization protocol: * @@ -595,6 +685,7 @@ public byte[] Serialize() /// public static GenTensor Deserialize(byte[] data) => Serializer.Deserialize(data); + #endregion } -} +} \ No newline at end of file diff --git a/GenericTensor/Functions/Determinant.cs b/GenericTensor/Functions/Determinant.cs index c3dce7a..d67448b 100644 --- a/GenericTensor/Functions/Determinant.cs +++ b/GenericTensor/Functions/Determinant.cs @@ -87,7 +87,7 @@ internal static T DeterminantGaussianSafeDivision(GenTensor t, int return t.GetValueNoCheck(0, 0); var n = diagLength; - var elemMatrix = EchelonForm.InnerGaussianEliminationSafeDivision(t, n, n, out var swapCount); + var elemMatrix = EchelonForm.InnerGaussianEliminationSafeDivision(t, n, n, null, out var swapCount); var det = default(EchelonForm.WrapperSafeDivisionWrapper).CreateOne(); for (int i = 0; i < n; i++) diff --git a/GenericTensor/Functions/EchelonForm.cs b/GenericTensor/Functions/EchelonForm.cs index b782158..7432d0b 100644 --- a/GenericTensor/Functions/EchelonForm.cs +++ b/GenericTensor/Functions/EchelonForm.cs @@ -1,4 +1,5 @@ #region copyright + /* * MIT License * @@ -22,6 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + #endregion @@ -34,11 +36,18 @@ namespace GenericTensor.Functions { internal static class EchelonFormExtensions { - internal static GenTensor SafeDivisionToSimple(this GenTensor.SafeDivisionWrapper, EchelonForm.WrapperSafeDivisionWrapper> t) where TWrapper : struct, IOperations + internal static GenTensor SafeDivisionToSimple( + this GenTensor.SafeDivisionWrapper, + EchelonForm.WrapperSafeDivisionWrapper> t) + where TWrapper : struct, IOperations => GenTensor.CreateMatrix(t.Shape[0], t.Shape[1], (x, y) => t.GetValueNoCheck(x, y).Count()); - internal static GenTensor.SafeDivisionWrapper, EchelonForm.WrapperSafeDivisionWrapper> SimpleToSafeDivision(this GenTensor t) where TWrapper : struct, IOperations - => GenTensor.SafeDivisionWrapper, EchelonForm.WrapperSafeDivisionWrapper> + internal static + GenTensor.SafeDivisionWrapper, + EchelonForm.WrapperSafeDivisionWrapper> + SimpleToSafeDivision(this GenTensor t) where TWrapper : struct, IOperations + => GenTensor.SafeDivisionWrapper, + EchelonForm.WrapperSafeDivisionWrapper> .CreateMatrix(t.Shape[0], t.Shape[1], (x, y) => new EchelonForm.SafeDivisionWrapper(t.GetValueNoCheck(x, y)) ); @@ -47,6 +56,7 @@ internal static GenTensor.SafeDivisionWrapper where TWrapper : struct, IOperations { #region Gaussian elimination safe division + internal struct SafeDivisionWrapper where TW : struct, IOperations { internal W num; @@ -63,28 +73,31 @@ public SafeDivisionWrapper(W num, W den) this.num = num; this.den = den; } - + public W Count() => default(TW).Divide(num, den); } - internal struct WrapperSafeDivisionWrapper : IOperations> where TW : struct, IOperations + internal struct WrapperSafeDivisionWrapper : IOperations> + where TW : struct, IOperations { - public SafeDivisionWrapper Add(SafeDivisionWrapper a, SafeDivisionWrapper b) { - return new SafeDivisionWrapper(default(TW).Add(default(TW).Multiply(a.num, b.den), default(TW).Multiply(b.num, a.den)), + return new SafeDivisionWrapper( + default(TW).Add(default(TW).Multiply(a.num, b.den), default(TW).Multiply(b.num, a.den)), default(TW).Multiply(a.den, b.den)); } public SafeDivisionWrapper Subtract(SafeDivisionWrapper a, SafeDivisionWrapper b) { - return new SafeDivisionWrapper(default(TW).Subtract(default(TW).Multiply(a.num, b.den), default(TW).Multiply(b.num, a.den)), + return new SafeDivisionWrapper( + default(TW).Subtract(default(TW).Multiply(a.num, b.den), default(TW).Multiply(b.num, a.den)), default(TW).Multiply(a.den, b.den)); } public SafeDivisionWrapper Multiply(SafeDivisionWrapper a, SafeDivisionWrapper b) { - return new SafeDivisionWrapper(default(TW).Multiply(a.num, b.num), default(TW).Multiply(a.den, b.den)); + return new SafeDivisionWrapper(default(TW).Multiply(a.num, b.num), + default(TW).Multiply(a.den, b.den)); } public SafeDivisionWrapper Negate(SafeDivisionWrapper a) @@ -94,7 +107,8 @@ public SafeDivisionWrapper Negate(SafeDivisionWrapper a) public SafeDivisionWrapper Divide(SafeDivisionWrapper a, SafeDivisionWrapper b) { - return new SafeDivisionWrapper(default(TW).Multiply(a.num, b.den), default(TW).Multiply(a.den, b.num)); + return new SafeDivisionWrapper(default(TW).Multiply(a.num, b.den), + default(TW).Multiply(a.den, b.num)); } public SafeDivisionWrapper CreateOne() @@ -143,31 +157,33 @@ public SafeDivisionWrapper Deserialize(byte[] data) } } - internal static GenTensor, WrapperSafeDivisionWrapper> InnerGaussianEliminationSafeDivision(GenTensor t, int m, int n, out int swapCount) + internal static GenTensor, WrapperSafeDivisionWrapper> + InnerGaussianEliminationSafeDivision(GenTensor t, int m, int n, int[]? permutations, out int swapCount) { var elemMatrix = t.SimpleToSafeDivision(); swapCount = 0; - EchelonForm, WrapperSafeDivisionWrapper>.InnerGaussianEliminationSimple(elemMatrix, 0, ref swapCount); + EchelonForm, WrapperSafeDivisionWrapper> + .InnerGaussianEliminationSimple(elemMatrix, 0, permutations, ref swapCount); return elemMatrix; } public static GenTensor RowEchelonFormSafeDivision(GenTensor t) { - #if ALLOW_EXCEPTIONS +#if ALLOW_EXCEPTIONS if (!t.IsMatrix) throw new InvalidShapeException("this should be matrix"); - #endif - var wrp = InnerGaussianEliminationSafeDivision(t, t.Shape[0], t.Shape[1], out _); +#endif + var wrp = InnerGaussianEliminationSafeDivision(t, t.Shape[0], t.Shape[1], null, out _); return wrp.SafeDivisionToSimple(); } internal static void InnerGaussianEliminationSimpleDiscardSwapCount(GenTensor t, int off) { var intoNowhere = 0; - InnerGaussianEliminationSimple(t, off, ref intoNowhere); + InnerGaussianEliminationSimple(t, off, null, ref intoNowhere); } - internal static void InnerGaussianEliminationSimple(GenTensor t, int off, ref int swapCount) + internal static void InnerGaussianEliminationSimple(GenTensor t, int off, int[]? permutations, ref int swapCount) { // Here we are sticking to the algorithm, // provided here: https://www.math.purdue.edu/~shao92/documents/Algorithm%20REF.pdf @@ -185,6 +201,9 @@ internal static void InnerGaussianEliminationSimple(GenTensor t, in { t.RowSwap(off, pivotId); swapCount++; + + if (permutations is not null) + (permutations[off], permutations[pivotId]) = (permutations[pivotId], permutations[off]); } @@ -200,7 +219,7 @@ internal static void InnerGaussianEliminationSimple(GenTensor t, in // VI. Let us apply the algorithm for the inner matrix - InnerGaussianEliminationSimple(t, off + 1, ref swapCount); + InnerGaussianEliminationSimple(t, off + 1, permutations, ref swapCount); static int? NonZeroColumn(GenTensor t, int c, int off) @@ -223,18 +242,47 @@ internal static void InnerGaussianEliminationSimple(GenTensor t, in public static GenTensor RowEchelonFormSimple(GenTensor t) { - #if ALLOW_EXCEPTIONS +#if ALLOW_EXCEPTIONS if (!t.IsMatrix) throw new InvalidShapeException("this should be matrix"); - #endif +#endif var res = t.Copy(copyElements: false); InnerGaussianEliminationSimpleDiscardSwapCount(res, 0); return res; } + public static (GenTensor, int[]) RowEchelonFormPermuteSimple(GenTensor t) + { +#if ALLOW_EXCEPTIONS + if (!t.IsMatrix) + throw new InvalidShapeException("this should be matrix"); +#endif + var res = t.Copy(copyElements: false); + var permute = new int[t.Shape[0]]; + for (var i = 0; i < permute.Length; i++) permute[i] = i + 1; + + var _ = 0; + InnerGaussianEliminationSimple(res, 0, permute, ref _); + return (res, permute); + } + + public static (GenTensor, int[]) RowEchelonFormPermuteSafeDivision(GenTensor t) + { +#if ALLOW_EXCEPTIONS + if (!t.IsMatrix) + throw new InvalidShapeException("this should be matrix"); +#endif + var permutations = new int[t.Shape[0]]; + for (var i = 0; i < permutations.Length; i++) permutations[i] = i + 1; + + var wrp = InnerGaussianEliminationSafeDivision(t, t.Shape[0], t.Shape[1], permutations, out _); + return (wrp.SafeDivisionToSimple(), permutations); + } + #endregion #region Row echelon form leading ones + private static GenTensor InnerRowEchelonFormLeadingOnes(GenTensor t) { var rowForm = t.Copy(copyElements: false); @@ -247,37 +295,39 @@ private static GenTensor InnerRowEchelonFormLeadingOnes(GenTensor RowEchelonFormLeadingOnesSimple(GenTensor t) { - #if ALLOW_EXCEPTIONS +#if ALLOW_EXCEPTIONS if (!t.IsMatrix) throw new InvalidShapeException("this should be matrix"); - #endif +#endif return InnerRowEchelonFormLeadingOnes(t); } public static GenTensor RowEchelonFormLeadingOnesSafeDivision(GenTensor t) { - #if ALLOW_EXCEPTIONS +#if ALLOW_EXCEPTIONS if (!t.IsMatrix) throw new InvalidShapeException("this should be matrix"); - #endif - return EchelonForm, WrapperSafeDivisionWrapper>.InnerRowEchelonFormLeadingOnes(t.SimpleToSafeDivision()).SafeDivisionToSimple(); +#endif + return EchelonForm, WrapperSafeDivisionWrapper> + .InnerRowEchelonFormLeadingOnes(t.SimpleToSafeDivision()).SafeDivisionToSimple(); } #endregion #region Reduced row echelon form - private static GenTensor InnerReducedRowEchelonForm(GenTensor t, out int swapCount) + private static GenTensor InnerReducedRowEchelonForm(GenTensor t, int[]? permutations, out int swapCount) { var upper = t.Copy(copyElements: false); swapCount = 0; - InnerGaussianEliminationSimple(upper, 0, ref swapCount); + InnerGaussianEliminationSimple(upper, 0, permutations, ref swapCount); for (int r = t.Shape[0] - 1; r >= 0; r--) { if (upper.RowGetLeadingElement(r) is not { } leading) continue; for (int i = 0; i < r; i++) - upper.RowSubtract(i, r, default(TWrapper).Divide(upper.GetValueNoCheck(i, leading.index), leading.value)); + upper.RowSubtract(i, r, + default(TWrapper).Divide(upper.GetValueNoCheck(i, leading.index), leading.value)); upper.RowMultiply(r, default(TWrapper).Divide(default(TWrapper).CreateOne(), leading.value)); } @@ -287,22 +337,38 @@ private static GenTensor InnerReducedRowEchelonForm(GenTensor ReducedRowEchelonFormSimple(GenTensor t) { - #if ALLOW_EXCEPTIONS +#if ALLOW_EXCEPTIONS if (!t.IsMatrix) throw new InvalidShapeException("this should be matrix"); - #endif - return InnerReducedRowEchelonForm(t, out _); +#endif + return InnerReducedRowEchelonForm(t, null, out _); } public static GenTensor ReducedRowEchelonFormSafeDivision(GenTensor t) { +#if ALLOW_EXCEPTIONS + if (!t.IsMatrix) + throw new InvalidShapeException("this should be matrix"); +#endif + return EchelonForm, WrapperSafeDivisionWrapper> + .InnerReducedRowEchelonForm(t.SimpleToSafeDivision(), null, out var _).SafeDivisionToSimple(); + } + + public static (GenTensor result, int[] permutations) ReducedRowEchelonFormPermuteSafeDivision(GenTensor t) + { #if ALLOW_EXCEPTIONS if (!t.IsMatrix) throw new InvalidShapeException("this should be matrix"); #endif - return EchelonForm, WrapperSafeDivisionWrapper>.InnerReducedRowEchelonForm(t.SimpleToSafeDivision(), out var _).SafeDivisionToSimple(); + + var permutation = new int[t.Shape[0]]; + for (var i = 0; i < permutation.Length; i++) permutation[i] = i + 1; + + return (EchelonForm, WrapperSafeDivisionWrapper> + .InnerReducedRowEchelonForm(t.SimpleToSafeDivision(), permutation, out var _).SafeDivisionToSimple(), + permutation); } #endregion } -} +} \ No newline at end of file diff --git a/GenericTensor/Functions/LuDecomposition.cs b/GenericTensor/Functions/LuDecomposition.cs new file mode 100644 index 0000000..5899cc2 --- /dev/null +++ b/GenericTensor/Functions/LuDecomposition.cs @@ -0,0 +1,90 @@ +#region copyright + +/* + * MIT License + * + * Copyright (c) 2020-2021 WhiteBlackGoose + * + * 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. + */ + +#endregion + + +using GenericTensor.Core; + +namespace GenericTensor.Functions +{ + internal static class LuDecomposition where TWrapper : struct, IOperations + { + public static (GenTensor, GenTensor) Decompose(GenTensor t) + { +#if ALLOW_EXCEPTIONS + if (!t.IsSquareMatrix) + throw new InvalidShapeException("this should be a square matrix"); +#endif + + var n = t.Shape[0]; + var lower = GenTensor.CreateMatrix(n, n); + var upper = GenTensor.CreateMatrix(n, n); + + var tw = default(TWrapper); + var zero = tw.CreateZero(); + var one = tw.CreateOne(); + + for (var i = 0; i < n; i++) + { + // Upper triangular + for (var k = i; k < n; k++) + { + var sum = zero; + for (var j = 0; j < i; j++) + sum = tw.Add(sum, tw.Multiply(lower[i, j], upper[j, k])); + + upper[i, k] = tw.Subtract(t[i, k], sum); + } + + // Lower triangular + for (var k = i; k < n; k++) + { + if (i == k) + lower[i, i] = one; // 1 at diagonals + else + { + var sum = zero; + for (var j = 0; j < i; j++) + sum = tw.Add(sum, tw.Multiply(lower[k, j], upper[j, i])); + + #if ALLOW_EXCEPTIONS + if (Equals(upper[i, i], zero)) + { + throw new ImpossibleDecomposition("There is no LU decomposition for given matrix"); + } + #endif + + lower[k, i] + = tw.Divide(tw.Subtract(t[k, i], sum), upper[i, i]); + } + } + } + + return (lower, upper); + } + } +} \ No newline at end of file diff --git a/GenericTensor/Functions/PluDecomposition.cs b/GenericTensor/Functions/PluDecomposition.cs new file mode 100644 index 0000000..d7356a7 --- /dev/null +++ b/GenericTensor/Functions/PluDecomposition.cs @@ -0,0 +1,68 @@ +#region copyright + +/* + * MIT License + * + * Copyright (c) 2020-2021 WhiteBlackGoose + * + * 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. + */ + +#endregion + + +using GenericTensor.Core; + +namespace GenericTensor.Functions +{ + internal static class PluDecomposition where TWrapper : struct, IOperations + { + public static (GenTensor, GenTensor, GenTensor) Decompose(GenTensor original) + { + var t = original.Copy(copyElements: true); + + #if ALLOW_EXCEPTIONS + if (!t.IsSquareMatrix) + throw new InvalidShapeException("this should be a square matrix"); + #endif + + var n = t.Shape[0]; + var m = t.Shape[1]; + var tw = default(TWrapper); + + var identity = GenTensor.CreateIdentityMatrix(m); + + t.TransposeMatrix(); + var adj = GenTensor.Concat(t, identity); + adj.TransposeMatrix(); + + var (echelon, permute) = adj.RowEchelonFormPermuteSafeDivision(); + var upper = GenTensor.CreateMatrix(n, m, (i, j) => echelon[i, j]); + var lowerZero = GenTensor.CreateMatrix(m, m, (i, j) => echelon[i, m + j]); + + lowerZero.InvertMatrix(); + + var permuteMatrix = + GenTensor.CreateMatrix(m, m, (i, j) => j == permute[i] - 1 ? tw.CreateOne() : tw.CreateZero()); + + var lower = GenTensor.MatrixMultiply(permuteMatrix, lowerZero); + return (permuteMatrix, lower, upper); + } + } +} \ No newline at end of file diff --git a/GenericTensor/GenericTensor.csproj b/GenericTensor/GenericTensor.csproj index bb9cd57..c168d09 100644 --- a/GenericTensor/GenericTensor.csproj +++ b/GenericTensor/GenericTensor.csproj @@ -18,6 +18,7 @@ tensor, generic, matrix, vector, performance true keypair.snk + enable diff --git a/GenericTensor/GenericTensor.csproj.DotSettings b/GenericTensor/GenericTensor.csproj.DotSettings deleted file mode 100644 index b9fd6ee..0000000 --- a/GenericTensor/GenericTensor.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp80 \ No newline at end of file diff --git a/README.md b/README.md index 6efd483..eda315f 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,32 @@ Finds the power of a matrix. Works for O(log(N) * N^3)

+#### Decompositions + +
PLU decomposision

+ +```cs +public (GenTensor p, GenTensor l, GenTensor u) PluDecomposition() +``` + +Find PLU decomposition: matrices P, L, U such that for original matrix A: PA = LU. + +Works for O(n^3) + +

+ +
LU decomposision

+ +```cs +public (GenTensor, GenTensor) LuDecomposition() +``` + +Find LU decomposition: matrices L, U such that for original matrix A: A = LU. + +Works for O(n^3) + +

+ #### Piecewise arithmetics
Tensor and Tensor

diff --git a/UnitTests/LuDecompositions.cs b/UnitTests/LuDecompositions.cs new file mode 100644 index 0000000..9118bfd --- /dev/null +++ b/UnitTests/LuDecompositions.cs @@ -0,0 +1,136 @@ +#region copyright + +/* + * MIT License + * + * Copyright (c) 2020-2021 WhiteBlackGoose + * + * 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. + */ + +#endregion + +using System; +using GenericTensor.Core; +using GenericTensor.Functions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests +{ + [TestClass] + public class LuDecompositions + { + private void AssertMatrixIsLowerTriangular(GenTensor t) where TW : struct, IOperations + { + Assert.IsTrue(t.IsSquareMatrix); + t.IterateOver2((i, j) => + { + if (i < j) Assert.AreEqual(t[i, j], default(TW).CreateZero()); + }); + } + + private void AssertMatrixIsUpperTriangular(GenTensor t) where TW : struct, IOperations + { + Assert.IsTrue(t.IsSquareMatrix); + t.IterateOver2((i, j) => + { + if (i > j) Assert.AreEqual(t[i, j], default(TW).CreateZero()); + }); + } + + private void TestProvidedLu(T[,] data) where TW : struct, IOperations + { + var m = GenTensor.CreateMatrix(data); + var (lower, upper) = m.LuDecomposition(); + + AssertMatrixIsLowerTriangular(lower); + AssertMatrixIsUpperTriangular(upper); + + Assert.AreEqual(GenTensor.MatrixMultiply(lower, upper), m); + } + + private void TestProvidedPlu(T[,] data) where TW : struct, IOperations + { + var m = GenTensor.CreateMatrix(data); + var (p, l, u) = m.PluDecomposition(); + + AssertMatrixIsLowerTriangular(l); + AssertMatrixIsUpperTriangular(u); + + Assert.AreEqual(GenTensor.MatrixMultiply(p, m), GenTensor.MatrixMultiply(l, u)); + } + + private static void TestNoLu(T[,] data) where TW : struct, IOperations => + Assert.ThrowsException(() => + GenTensor.CreateMatrix(data).LuDecomposition()); + + [TestMethod] + public void LuArbitrary_1() => + TestProvidedLu(new[,] + { + { 1, 2, 4 }, + { 3, 8, 14 }, + { 2, 6, 13 }, + }); + + [TestMethod] + public void LuArbitrary_2() => + TestProvidedLu(new[,] + { + { -6, -4 }, + { -6, -4 } + }); + + [TestMethod] + public void LuArbitrary_3() => + TestNoLu(new[,] + { + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 } + }); + + [TestMethod] + public void PluArbitrary_1() => + TestProvidedPlu(new[,] + { + { 1, 2, 4 }, + { 3, 8, 14 }, + { 2, 6, 13 } + }); + + [TestMethod] + public void PluArbitrary_2() => + TestProvidedPlu(new[,] + { + { 0D, 1, 0 }, + { -8, 8, 1 }, + { 2, -2, 0 } + }); + + [TestMethod] + public void PluArbitrary_3() => + TestProvidedPlu(new[,] + { + {1, 0, 4}, + {0, 0, 1}, + {3, 2, 13} + }); + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 9dc2d9a..3954fdb 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -1,9 +1,11 @@  - net5.0 + netcoreapp3.1 false + + 9