Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ and a number that passes every round is left open, since passing is not a proof
| `"(10^30 + 1) in PP".ToEntity().Evaled` | `1000000000000000000000000000001 in PP` | `False` |
| `"(10^30 + 57) in PP".ToEntity().Evaled` | `1000000000000000000000000000057 in PP` | `1000000000000000000000000000057 in PP` |

### The row echelon forms divide exactly

**Not silent for numbers: what threw or did not return now answers. Silent for symbols: the forms are written
otherwise.** Both forms were computed by GenericTensor's SafeDivision elimination, which keeps every entry as a numerator
and a denominator that are never reduced against each other, so their sizes grew with every row operation. For a 4x4 of
small rationals, the reduced form and `Rank` could throw `InvalidCastException`, and an 8x8 did not return. Both now divide as they go
([#2270](https://github.com/asc-community/AngouriMath/issues/2270)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"[[1/2, -2/3, 0, -2/3], [3, -2, 0, -2/3], [2, 2, 1, -3], [-2/3, 0, -2/3, 1]]"`, `.Rank` | throws `InvalidCastException` | `4` |
| the same, `.ReducedRowEchelonForm` | throws `InvalidCastException` | the identity |
| `"[[a, b], [c, d]]"`, `.RowEchelonForm` | `[[a, b], [(c * a + -c * a) / a, (d * a + -c * b) / a]]` | `[[a, b], [c + -c / a * a, d + -c / a * b]]` |

### `arcsin` and `arccos` of a real number past 1 have their value

**Silent: `arcsin(x)` and `arccos(x)` of a real `x` past 1 are `sign(x) pi/2 - i arccosh|x|` and `pi/2 - arcsin(x)`,
Expand Down
12 changes: 10 additions & 2 deletions Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,17 @@ public IEnumerator<Entity> GetEnumerator()
/// <summary>
/// Matrix's form, transformed via Gaussian elimination.
/// </summary>
/// <remarks>
/// Both forms divide as they go. GenericTensor's SafeDivision variants keep every entry as a numerator and a
/// denominator that are never reduced against each other, which is for element types whose division truncates,
/// and an Entity divides exactly. Unreduced, the pairs grew with every row operation and the back substitution
/// squared them: one 7x7 of small entries took two minutes, and an 8x8 did not return in ten, and the 4x4
/// of https://github.com/asc-community/AngouriMath/issues/2270 ended with a denominator near 1e-246, which
/// https://github.com/asc-community/AngouriMath/issues/2277 made a NaN.
/// </remarks>
public Matrix RowEchelonForm =>
@ref.GetValue(static @this =>
OfTheShapeOf(@this, new Matrix(@this.InnerMatrix.RowEchelonFormSafeDivision()).InnerSimplified),
OfTheShapeOf(@this, new Matrix(@this.InnerMatrix.RowEchelonFormSimple()).InnerSimplified),
this);
private LazyPropertyA<Matrix> @ref;

Expand All @@ -593,7 +601,7 @@ public IEnumerator<Entity> GetEnumerator()
/// </summary>
public Matrix ReducedRowEchelonForm =>
rref.GetValue(static @this =>
OfTheShapeOf(@this, new Matrix(@this.InnerMatrix.ReducedRowEchelonFormSafeDivision()).InnerSimplified),
OfTheShapeOf(@this, new Matrix(@this.InnerMatrix.ReducedRowEchelonFormSimple()).InnerSimplified),
this);
private LazyPropertyA<Matrix> rref;

Expand Down
44 changes: 44 additions & 0 deletions Sources/Tests/UnitTests/Algebra/MatrixTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,50 @@ [Fact] public void Rank2()
Assert.Equal(6, MathS.IdentityMatrix(6).Rank);
}

// The elimination kept each entry as an unreduced numerator and denominator, the
// denominator of row 0 reached 1e-246, and 0 over it was NaN: the form reduced to NaN and
// Rank threw InvalidCastException. https://github.com/asc-community/AngouriMath/issues/2270
[Theory]
[InlineData("[[1/2, -2/3, 0, -2/3], [3, -2, 0, -2/3], [2, 2, 1, -3], [-2/3, 0, -2/3, 1]]")]
[InlineData("[[-2/3, 1, 1, -2], [1/2, -2/3, -2/3, -2/3], [-2/3, -2/3, 1, -1], [1/2, -2, -3, -2/3]]")]
[InlineData("[[-2/3, 1/2, -2/3, 1/2], [1, -2/3, -2/3, -2], [1, -2/3, 1, -3], [-2, -2/3, -1, -2/3]]")]
public void AnInvertibleMatrixReducesToTheIdentity(string matrix)
{
var m = (Matrix)matrix.ToEntity();
Assert.Equal(MathS.IdentityMatrix(4), m.ReducedRowEchelonForm);
Assert.Equal(4, m.Rank);
}

// Unreduced, the back substitution squared the pairs' sizes at every row, and this did not
// return in two minutes; bounded, so that it fails rather than hangs the run.
// https://github.com/asc-community/AngouriMath/issues/2270
[Fact] public void AnEightByEightReduces()
{
var reduced = System.Threading.Tasks.Task.Factory.StartNew(Reduce,
System.Threading.CancellationToken.None, System.Threading.Tasks.TaskCreationOptions.LongRunning,
System.Threading.Tasks.TaskScheduler.Default);
Assert.True(reduced.Wait(System.TimeSpan.FromSeconds(60)), "the reduced row echelon form of an 8x8 did not return");
Assert.Equal(MathS.IdentityMatrix(8), reduced.Result.Reduced);
Assert.Equal(8, reduced.Result.Rank);
}

private static (Matrix Reduced, int Rank) Reduce()
{
var m = MathS.Matrix(new Entity[,]
{
{ 2, -1, 3, 1, -2, 1, 3, -3 },
{ 1, 2, -1, 3, 1, -2, 1, 2 },
{ -3, 1, 2, -1, 3, 2, -2, 1 },
{ 1, -2, 1, 2, -1, 3, 2, -1 },
{ 2, 3, -2, 1, 2, -1, -3, 1 },
{ -1, 1, 3, -2, 1, 2, 1, 3 },
{ 3, -1, 1, 2, -3, 1, 2, -2 },
{ 1, 2, 2, -1, 1, -3, 1, 1 },
});
Assert.NotEqual(Number.Integer.Zero, m.Determinant);
return (m.ReducedRowEchelonForm, m.Rank);
}

[Theory]
[InlineData("[a, b]", "[c, d]", "[a c, a d, b c, b d]")]
[InlineData("[[a, b], [c, d]]", "[[e, f], [g, h]]", "[ [a e, a f, b e, b f], [a g, a h, b g, b h], [c e, c f, d e, d f], [c g, c h, d g, d h] ]")]
Expand Down
Loading