diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs index 9b1fc065b..1cbb21ed1 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs @@ -376,6 +376,35 @@ public Entity AsScalar() return polynomial ?? 0; } + /// + /// The eigenvalues of this square matrix: the roots of its + /// , as a set, a repeated root once. + /// where the matrix is not square. + /// + /// + /// The characteristic polynomial divides by nothing, so its roots carry no condition an + /// elimination's pivots would bring: [[0, J], [J, 0]] has sqrt(J^2) and + /// -sqrt(J^2), which hold at J = 0 too. Up to the fourth degree the roots are + /// in radicals, and past it where the polynomial factors; otherwise the set is the one the + /// equation solver leaves. + /// #1676 + /// + /// + /// + /// Console.WriteLine(MathS.Matrix(new Entity[,] { { 2, 0, 0 }, { 0, 3, 4 }, { 0, 4, 9 } }).Eigenvalues); + /// + /// Prints + /// + /// { 1, 2, 11 } + /// + /// + public Set? Eigenvalues => eigenvalues.GetValue(static @this => + { + var lambda = Variable.CreateUnique(@this, "lambda"); + return @this.CharacteristicPolynomial(lambda) is { } polynomial ? polynomial.SolveEquation(lambda) : null; + }, this); + private LazyPropertyA eigenvalues; + /// Returns an inverse matrix if it exists public Matrix? Inverse => inverse.GetValue(static @this => { diff --git a/Sources/MCP/AngouriMath.Mcp/Matrices.cs b/Sources/MCP/AngouriMath.Mcp/Matrices.cs index 5b99911bd..9cd6dd92b 100644 --- a/Sources/MCP/AngouriMath.Mcp/Matrices.cs +++ b/Sources/MCP/AngouriMath.Mcp/Matrices.cs @@ -136,8 +136,8 @@ public static (Matrix Value, List Guards) Clean(Matrix m) } /// - /// Eigenvalues via the characteristic polynomial, which belongs in the library: - /// https://github.com/asc-community/AngouriMath/issues/1676 + /// The characteristic polynomial, det(A - lambda I), and the library's eigenvalues, + /// `Matrix.Eigenvalues`. https://github.com/asc-community/AngouriMath/issues/1676 /// /// The guard-dropping is not cosmetic. A determinant taken by dividing by pivots leaves a /// `provided` guard for each one, and on a symbolic matrix those are artefacts of the @@ -163,12 +163,15 @@ public static Eigen Compute(Matrix a, Variable lambda) Entity? roots; try { - // The solver reintroduces pivot guards of its own, so the result is cleaned - // again — stripping only the characteristic polynomial leaves eigenvalues - // reading 'J provided not J = 0'. - var (cleaned, solverGuards) = Clean(poly.SolveEquation(lambda).Simplify()); - roots = cleaned; - guards.AddRange(solverGuards); + // The library's, from a characteristic polynomial that divides by nothing; cleaned + // all the same, so that a guard any later route brings is reported rather than shown. + if (a.Eigenvalues is { } eigenvalues) + { + var (cleaned, solverGuards) = Clean(eigenvalues.Simplify()); + roots = cleaned; + guards.AddRange(solverGuards); + } + else roots = null; } catch { roots = null; } diff --git a/Sources/MCP/README.md b/Sources/MCP/README.md index cfd5afc61..3da4fb593 100644 --- a/Sources/MCP/README.md +++ b/Sources/MCP/README.md @@ -151,8 +151,5 @@ existing tool, then a prompt, and add a tool last. A tool's description is a rou documentation: say when to call it, and where a model would wrongly trust itself, say so. This is an adapter, not a second computer algebra system. Mathematics belongs in the library, where -the terminal and the notebooks get it too, and three workarounds here wait for it: -eigenvalues by the characteristic polynomial ([#1676](https://github.com/asc-community/AngouriMath/issues/1676)), -a numeric comparison with a tolerance ([#1674](https://github.com/asc-community/AngouriMath/issues/1674)), -and the numeric definite integral run twice to find its good digits -([#1675](https://github.com/asc-community/AngouriMath/issues/1675)). +the terminal and the notebooks get it too. Where the server has to work around the library, the +workaround links the library's issue in a comment beside it, and the fix for that issue removes it. diff --git a/Sources/Tests/UnitTests/Algebra/EigenvaluesTest.cs b/Sources/Tests/UnitTests/Algebra/EigenvaluesTest.cs new file mode 100644 index 000000000..01be746d6 --- /dev/null +++ b/Sources/Tests/UnitTests/Algebra/EigenvaluesTest.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System.Linq; +using AngouriMath.Extensions; +using Xunit; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Set; + +namespace AngouriMath.Tests.Algebra +{ + /// + /// : the roots of the characteristic polynomial, which divides by nothing, + /// so no pivot leaves a condition on them. Each one is checked by making A - lambda I singular. + /// #1676 + /// + [Trait("Area", "Algebra")] + public sealed class EigenvaluesTest + { + [Fact] + public void OfASymmetricMatrixAreItsRealRoots() + => Assert.Equal(new FiniteSet(1, 2, 11), MathS.Matrix(new Entity[,] { { 2, 0, 0 }, { 0, 3, 4 }, { 0, 4, 9 } }).Eigenvalues); + + [Fact] + public void ARepeatedRootIsOneMember() + => Assert.Equal(new FiniteSet(1), MathS.Matrix(new Entity[,] { { 1, 1 }, { 0, 1 } }).Eigenvalues); + + [Fact] + public void OfASymbolicMatrixCarryNoPivotCondition() + { + var eigenvalues = MathS.Matrix(new Entity[,] { { 0, "J" }, { "J", 0 } }).Eigenvalues; + Assert.Equal(new FiniteSet("sqrt(J^2)", "-sqrt(J^2)"), eigenvalues); + Assert.DoesNotContain("provided", eigenvalues!.Stringize()); + } + + [Fact] + public void OfANonSquareMatrixAreNull() + => Assert.Null(MathS.Matrix(new Entity[,] { { 1, 2, 3 }, { 4, 5, 6 } }).Eigenvalues); + + [Theory] + [InlineData(1, 2, 3, 4)] + [InlineData(2, -1, 5, 7)] + [InlineData(0, 1, -1, 0)] + public void EachMakesTheShiftedMatrixSingular(int a, int b, int c, int d) + { + var matrix = MathS.Matrix(new Entity[,] { { a, b }, { c, d } }); + var eigenvalues = Assert.IsType(matrix.Eigenvalues); + Assert.NotEmpty(eigenvalues); + foreach (var lambda in eigenvalues) + { + var shifted = MathS.Matrix(new Entity[,] { { a - lambda, b }, { c, d - lambda } }); + Assert.True(shifted.Determinant!.EvalNumerical().Abs().EDecimal.ToDouble() < 1e-12, + $"det(A - {lambda} I) is {shifted.Determinant.EvalNumerical()}"); + } + } + } +} diff --git a/Sources/Tests/UnitTests/Common/PublicApi.txt b/Sources/Tests/UnitTests/Common/PublicApi.txt index b48d96118..a9955985f 100644 --- a/Sources/Tests/UnitTests/Common/PublicApi.txt +++ b/Sources/Tests/UnitTests/Common/PublicApi.txt @@ -1415,6 +1415,7 @@ AngouriMath.Entity+Matrix.CharacteristicPolynomial(AngouriMath.Entity+Variable) AngouriMath.Entity+Matrix.Codomain { } : AngouriMath.Core.Domain AngouriMath.Entity+Matrix.ColumnCount { } : System.Int32 AngouriMath.Entity+Matrix.Determinant { } : AngouriMath.Entity +AngouriMath.Entity+Matrix.Eigenvalues { } : AngouriMath.Entity+Set AngouriMath.Entity+Matrix.EqualityContract { } : System.Type AngouriMath.Entity+Matrix.Equals(AngouriMath.Entity) : System.Boolean AngouriMath.Entity+Matrix.Equals(AngouriMath.Entity+Matrix) : System.Boolean