diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index a3ed4d206..5d84402f0 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -42,6 +42,21 @@ A not-equal written `<>` is read as `not (a = b)`, and printed that way. ## Unreleased — since 2.6.0 +### An equation through a negative whole power is not answered with no roots + +**Silent: an empty answer becomes the roots.** Inverting `x^n = v` for a negative whole `n` multiplied by the roots +of unity of order `n`, and there are none of order `-2`, so the inversion found no roots, and an equation that reached +one inside another was answered with the empty set, a claim that it has no solution. `x^n = v` has the roots of +`x^|n| = 1/v`, and they are found now. `x^(-2) = 4` on its own was already answered by another route +([#2144](https://github.com/asc-community/AngouriMath/issues/2144)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"1/(1 + x^(-2)) = 1/2".Solve("x")` | `{ }` | `{ 1, -1 }` | +| `"2/(1 + x^(-2)) = 1".Solve("x")` | `{ }` | `{ 1, -1 }` | +| `"1/(2 + p^(-3)) = 1/3".Solve("p")` | `{ }` | `{ 1, -1/2 + i * 1/2 * sqrt(3), -1/2 + i * -1/2 * sqrt(3) }` | +| `"1/(1 + x^(-2)) = y".Solve("x")` | `{ }` | `{ ((-1) / (-y) - 1) ^ (-1/2), -((-1) / (-y) - 1) ^ (-1/2) }` | + ### A system with a product equation is solved branch by branch **Silent: solutions that were missing are answered.** `EquationSystem.Solve` eliminated `x * y = 0` for `y` as `0`, diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 92f736e7c..1e64fda62 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -273,6 +273,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/DocCommentPlacementTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Algebra/SolveTest/NegativePowerInversionTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Common/LambdaArrowSyntaxTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs index 864df3dd2..cafd6382f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs @@ -103,7 +103,19 @@ partial record Powf if (Base.ContainsNode(x)) { if (Exponent is Integer { EInteger: var pow }) - return Together(Number.GetAllRootsOf1(pow).Select(root => Base.Invert(root * MathS.Pow(value, 1 / Exponent), x))); + { + // x^n = v has |n| roots for a negative n as well, those of x^|n| = 1/v: + // each n-th root of v times a root of unity of order |n|, whose n-th + // power is 1. GetAllRootsOf1 counts up to its argument, so given -2 it + // gave no roots at all, and 1/(1 + x^(-2)) = 1/2 was answered { }. A + // negative power is never zero, so x^(-2) = 0 is the one that has none. + // https://github.com/asc-community/AngouriMath/issues/2144 + if (pow.IsZero) + return null; + if (pow.Sign < 0 && value.Evaled is Complex { IsZero: true }) + return Enumerable.Empty(); + return Together(Number.GetAllRootsOf1(pow.Abs()).Select(root => Base.Invert(root * MathS.Pow(value, 1 / Exponent), x))); + } else return Base.Invert(MathS.Pow(value, 1 / Exponent), x); } diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/NegativePowerInversionTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/NegativePowerInversionTest.cs new file mode 100644 index 000000000..63720be23 --- /dev/null +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/NegativePowerInversionTest.cs @@ -0,0 +1,63 @@ +// +// 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 AngouriMath; +using AngouriMath.Extensions; +using System.Linq; +using Xunit; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Set; + +namespace AngouriMath.Tests.Algebra.SolveTest +{ + /// + /// Inverting a negative whole power found no roots: the roots of unity it multiplies by were + /// counted up to the exponent, and up to -2 is none. So an equation that reaches one inside + /// another, as 1/(1 + x^(-2)) = 1/2 does, was answered with the empty set, a claim that + /// it has no roots, where it has 1 and -1. + /// #2144 + /// + [Trait("Area", "Algebra")] + public sealed class NegativePowerInversionTest + { + private static Set Solve(string statement, string x = "x") => statement.ToEntity().Solve(x); + + private static Entity IsIn(string value, Set set) => value.ToEntity().In(set).Simplify(); + + [Theory] + [InlineData("1/(1 + x^(-2)) = 1/2", "1", "-1", "2")] + [InlineData("1/(2 + x^(-3)) = 1/3", "1", "-1/2 + 1/2 * sqrt(3) * i", "-1")] + [InlineData("2/(1 + x^(-2)) = 1", "1", "-1", "0")] + [InlineData("1/(3 + (x + 1)^(-2)) = 1/4", "0", "-2", "1")] + public void EveryRootIsFound(string equation, string root, string otherRoot, string nonRoot) + { + var solved = Solve(equation); + Assert.Equal(Entity.Boolean.True, IsIn(root, solved)); + Assert.Equal(Entity.Boolean.True, IsIn(otherRoot, solved)); + Assert.Equal(Entity.Boolean.False, IsIn(nonRoot, solved)); + } + + /// + /// With a symbol on the right the answer is the two roots in terms of it, which at + /// y = 1/2 are the roots above. + /// + [Fact] + public void TheRootsInASymbolAreTheRootsAtEachValue() + { + var solved = Assert.IsType(Solve("1/(1 + x^(-2)) = y")); + Assert.Equal(2, solved.Count); + var atAHalf = solved.Select(root => root.Substitute("y", "1/2".ToEntity()).Simplify()).ToList(); + Assert.Contains(atAHalf, root => root == 1); + Assert.Contains(atAHalf, root => root == -1); + } + + /// A negative power is never zero, so this one does have none. + [Fact] + public void ANegativePowerIsNeverZero() + => Assert.Equal(Set.Empty, Solve("1/(1 + x^(-2)) = 1")); + } +}