From d2c865ccced2de02f7b8900b74c2443fb5898c4e Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 1 Nov 2026 15:34:19 +0000 Subject: [PATCH] An odd root of a negative real is a preimage, as the evaluator takes it #212 states the sector the principal power z^p takes its values in, -p pi < arg <= p pi for 0 < p < 1, and leaves a root of x^p = v only where v is in it. The evaluator does not take every power as principal: a negative real to a rational power whose denominator is odd and below ten is the real root to the numerator, as Number.Pow's search for a good root finds it, so (-8)^(1/3) is -2. So x^(1/3) = -2, x^(1/5) = -2 and x^(-1/3) = -1/2, which 2.6.0 answered { -8 }, { -32 } and { -8 }, were answered { }, the value being outside the sector. With an odd numerator the negative reals are values as well, and the condition gains arg(v) = pi; a value known to be real is then always in range. With an even numerator a negative base has a positive power, (-8)^(2/3) = 4, so a positive value has the negative of its principal preimage as well: x^(2/3) = 4 is { 8, -8 }, where 2.6.0 had { 8 }. A denominator of ten or more, or an even one, is the principal power, as the evaluator's is. The test that pinned x^(1/3) = a as no root at a = -1 pinned this: (-1)^3 = -1, and (-1)^(1/3) is -1. Found by a cross-check of equations in square and cube roots against a Newton search. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 10 ++-- .../EquationSolver/InvertNode.Classes.cs | 47 ++++++++++++++++--- .../SolveTest/FractionalPowerRangeTest.cs | 17 ++++++- 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e11efd135..3ebfcdd3c 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -141,8 +141,11 @@ the square root the solver takes after solving for `x^2`, where an equation has for `0 < p < 1` its values lie in the sector `-p pi < arg <= p pi`, with `0`, and for `-1 < p < 0` in `p pi <= arg < -p pi`, without `0`. `x^p = a` was solved to `a^(1/p)` with no condition, which is no root where `a` is outside the sector: `sqrt(x) = a` was `{ a ^ 2 }`, and at `a = -1` that is `1`, whose square root is `1` and not `-1`. The root now carries -the sector, written with `arg`, as `abs`'s inversion carries its own condition. A number on the other side is decided -as before, and `|p| > 1` needs no condition, since every value is then a power. What the equation is provided is known +the sector, written with `arg`, as `abs`'s inversion carries its own condition. A negative real to a rational power +whose denominator is odd and below ten is the real root to the numerator, as the evaluator gives it, `(-8)^(1/3)` being +`-2`: with an odd numerator the negative reals are values as well, so `x^(1/3) = -2` is still `{ -8 }`, and with an even +one a positive value has a negative preimage too. A number on the other side is decided as before, and `|p| > 1` needs +no condition, since every value is then a power. What the equation is provided is known while it is solved, as a provided expression is differentiated: a value known to be real, or declared real, has the half-line for its sector, one known to lie on it needs no condition, and one known to lie off it has no root. A conjunct of a condition that another conjunct already forces is not stated, so `a > -5 and a >= 0` is `a >= 0`, and a condition stated twice is @@ -153,7 +156,8 @@ carried it once combined: a second derivative of `1/sqrt(x^2 + 13)` stated `not | Input | Was (2.6.0) | Now | |---|---|---| | `"sqrt(x) = a".ToEntity().Solve("x")` | `{ a ^ 2 }` | `{ a ^ 2 provided a = 0 or -1/2 * pi < arg(a) and arg(a) <= 1/2 * pi }` | -| `"x^(1/3) = a".ToEntity().Solve("x")` | `{ a ^ 3 }` | `{ a ^ 3 provided a = 0 or -1/3 * pi < arg(a) and arg(a) <= 1/3 * pi }` | +| `"x^(1/3) = a".ToEntity().Solve("x")` | `{ a ^ 3 }` | `{ a ^ 3 provided a = 0 or -1/3 * pi < arg(a) and arg(a) <= 1/3 * pi or arg(a) = pi }` | +| `"x^(2/3) = 4".ToEntity().Solve("x")` | `{ 8 }` | `{ 8, -8 }` | | `"x^(-1/2) = a".ToEntity().Solve("x")` | `{ a ^ (-2) }` | `{ a ^ (-2) provided not a = 0 and -1/2 * pi <= arg(a) and arg(a) < 1/2 * pi }` | | `"(sqrt(x) = a) provided a in RR".ToEntity().Solve("x")` | `{ a ^ 2 provided a in RR }` | `{ a ^ 2 provided a in RR and a >= 0 }` | | `"(sqrt(x) = a) provided a > 0".ToEntity().Solve("x")` | `{ a ^ 2 provided a > 0 }` | `{ a ^ 2 provided a > 0 }` | diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs index 1a296e8c4..c9bc0017e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs @@ -116,9 +116,16 @@ partial record Powf 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) - ?.Select(root => root.Provided(InTheRange(value))); + var principal = Base.Invert(MathS.Pow(value, 1 / Exponent), x) + ?.Select(root => root.Provided(InTheRange(value))); + // With an even numerator a negative base has a positive power, (-8)^(2/3) = 4, + // so a positive value has the negative of its principal preimage for a + // preimage as well: x^(2/3) = 4 is 8 and -8. + if (principal is { } && TakesTheRealRootOfANegative(out var oddNumerator) && !oddNumerator) + return Base.Invert(-MathS.Pow(value, 1 / Exponent), x) is { } negative + ? principal.Concat(negative.Select(root => root.Provided(new Greaterf(value, Integer.Zero)))) + : null; + return principal; } // a ^ x = value => x = log(a, value) // TODO: determine how we should return periodic roots as @@ -138,12 +145,21 @@ private Entity InTheRange(Entity value) if (Exponent.Evaled is not Real { EDecimal.IsFinite: true } p || p.EDecimal.Abs().CompareTo(PeterO.Numbers.EDecimal.One) >= 0) return Boolean.True; + // A negative base with an odd numerator has a negative power, (-8)^(1/3) = -2, so + // the negative reals are in the range as well, and x^(1/3) = -2 is x = -8, as it + // was before the sector was stated. + var reachesTheNegatives = TakesTheRealRootOfANegative(out var oddNumerator) && oddNumerator; // Where the value is known to be real -- by its codomain, or by what the equation // it came from was provided -- arg(value) is 0 or pi, so the sector is the half-line // value >= 0 for p > 0 and value > 0 for p < 0, and nothing is stated where that is - // known as well: sqrt(x) = a provided a > 0 is a ^ 2 provided a > 0. - Entity halfLine = p.IsPositive ? new GreaterOrEqualf(value, Integer.Zero) : new Greaterf(value, Integer.Zero); - Entity outside = p.IsPositive ? new Lessf(value, Integer.Zero) : new LessOrEqualf(value, Integer.Zero); + // known as well: sqrt(x) = a provided a > 0 is a ^ 2 provided a > 0. With the + // negatives it is every real, or every real but 0. + Entity halfLine = reachesTheNegatives + ? (p.IsPositive ? Boolean.True : !value.EqualTo(Integer.Zero)) + : p.IsPositive ? new GreaterOrEqualf(value, Integer.Zero) : new Greaterf(value, Integer.Zero); + Entity outside = reachesTheNegatives + ? (p.IsPositive ? Boolean.False : value.EqualTo(Integer.Zero)) + : p.IsPositive ? new Lessf(value, Integer.Zero) : new LessOrEqualf(value, Integer.Zero); if (TreeAnalyzer.IsKnown(halfLine)) return Boolean.True; if (TreeAnalyzer.IsKnown(outside)) @@ -153,9 +169,26 @@ private Entity InTheRange(Entity value) var bound = (Exponent * MathS.pi).InnerSimplified; var opposite = ((-Exponent).InnerSimplified * MathS.pi).InnerSimplified; var angle = MathS.Arg(value); - return p.IsPositive + Entity sector = p.IsPositive ? value.EqualTo(Integer.Zero) | (new Lessf(opposite, angle) & new LessOrEqualf(angle, bound)) : !value.EqualTo(Integer.Zero) & new LessOrEqualf(bound, angle) & new Lessf(angle, opposite); + return reachesTheNegatives ? sector | angle.EqualTo(MathS.pi) : sector; + } + + /// + /// Whether a negative real base has a real power, as the evaluator takes it: a rational + /// power whose denominator is odd and below ten is the real root of a negative real to + /// the numerator, as Number.Pow's search for a good root finds it, and any other is the + /// principal power. (-8)^(1/3) is -2, (-8)^(2/3) is 4, and (-1)^(1/11) is not real. + /// + private bool TakesTheRealRootOfANegative(out bool oddNumerator) + { + oddNumerator = false; + if (Exponent is not Rational { ERational: var ratio } || ratio.Denominator.IsEven + || ratio.Denominator.CompareTo(PeterO.Numbers.EInteger.FromInt32(10)) >= 0) + return false; + oddNumerator = !ratio.Numerator.IsEven; + return true; } } diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/FractionalPowerRangeTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/FractionalPowerRangeTest.cs index 788b876f5..b303032eb 100644 --- a/Sources/Tests/UnitTests/Algebra/SolveTest/FractionalPowerRangeTest.cs +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/FractionalPowerRangeTest.cs @@ -19,6 +19,12 @@ namespace AngouriMath.Tests.Algebra /// solution. sqrt(x) = a was answered { a ^ 2 }, which is wrong at /// a = -1: sqrt((-1)^2) is 1. /// #212 + /// + /// A negative real to a rational power with an odd denominator below ten is the real root to + /// the numerator, which is what the evaluator gives: (-8)^(1/3) is -2. So with + /// an odd numerator the negative reals are values too, and x^(1/3) = -2 is + /// x = -8, which the sector alone answered { }; and with an even one a positive + /// value has a negative preimage as well, (-8)^(2/3) = 4. /// [Trait("Area", "Algebra")] public sealed class FractionalPowerRangeTest @@ -40,7 +46,8 @@ private static Entity OnlyRoot(string equation) [InlineData("sqrt(x) = a", "-i", false)] [InlineData("sqrt(x) = a", "-1", false)] [InlineData("x^(1/3) = a", "1 + i", true)] - [InlineData("x^(1/3) = a", "-1", false)] + [InlineData("x^(1/3) = a", "-1", true)] + [InlineData("x^(1/3) = a", "-i", false)] [InlineData("x^(-1/2) = a", "-i", true)] [InlineData("x^(-1/2) = a", "i", false)] [InlineData("x^(-1/2) = a", "0", false)] @@ -63,6 +70,14 @@ public void TheRootHoldsWhereItsConditionDoes(string equation, string value, boo [InlineData("sqrt(x) = -1", "{ }")] [InlineData("sqrt(x) = i", "{ -1 }")] [InlineData("sqrt(x) = -i", "{ }")] + [InlineData("x^(1/3) = -2", "{ -8 }")] + [InlineData("x^(1/5) = -2", "{ -32 }")] + [InlineData("x^(-1/3) = -1/2", "{ -8 }")] + [InlineData("x^(3/5) = -8", "{ -32 }")] + [InlineData("x^(2/3) = 4", "{ 8, -8 }")] + [InlineData("x^(4/3) = 16", "{ 8, -8 }")] + [InlineData("x^(2/3) = -4", "{ }")] + [InlineData("x^(1/11) = -1", "{ }")] public void ANumberIsDecided(string equation, string expected) => Assert.Equal(expected.ToEntity(), equation.ToEntity().Solve("x"));