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