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