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
29 changes: 29 additions & 0 deletions Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,35 @@ public Entity AsScalar()
return polynomial ?? 0;
}

/// <summary>
/// The eigenvalues of this square matrix: the roots of its
/// <see cref="CharacteristicPolynomial"/>, as a set, a repeated root once.
/// <see langword="null"/> where the matrix is not square.
/// </summary>
/// <remarks>
/// The characteristic polynomial divides by nothing, so its roots carry no condition an
/// elimination's pivots would bring: <c>[[0, J], [J, 0]]</c> has <c>sqrt(J^2)</c> and
/// <c>-sqrt(J^2)</c>, which hold at <c>J = 0</c> 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.
/// <a href="https://github.com/asc-community/AngouriMath/issues/1676">#1676</a>
/// </remarks>
/// <example>
/// <code>
/// Console.WriteLine(MathS.Matrix(new Entity[,] { { 2, 0, 0 }, { 0, 3, 4 }, { 0, 4, 9 } }).Eigenvalues);
/// </code>
/// Prints
/// <code>
/// { 1, 2, 11 }
/// </code>
/// </example>
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<Set?> eigenvalues;

/// <summary>Returns an inverse matrix if it exists</summary>
public Matrix? Inverse => inverse.GetValue(static @this =>
{
Expand Down
19 changes: 11 additions & 8 deletions Sources/MCP/AngouriMath.Mcp/Matrices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ public static (Matrix Value, List<string> Guards) Clean(Matrix m)
}

/// <summary>
/// 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
Expand All @@ -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; }

Expand Down
7 changes: 2 additions & 5 deletions Sources/MCP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
61 changes: 61 additions & 0 deletions Sources/Tests/UnitTests/Algebra/EigenvaluesTest.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// <see cref="Matrix.Eigenvalues"/>: the roots of the characteristic polynomial, which divides by nothing,
/// so no pivot leaves a condition on them. Each one is checked by making <c>A - lambda I</c> singular.
/// <a href="https://github.com/asc-community/AngouriMath/issues/1676">#1676</a>
/// </summary>
[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<FiniteSet>(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()}");
}
}
}
}
1 change: 1 addition & 0 deletions Sources/Tests/UnitTests/Common/PublicApi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading