From 8ac2297012f6014cbf96c3d35c82b0a943e10688 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 31 Oct 2026 23:10:13 +0000 Subject: [PATCH] Over the complex numbers, an equation in and out of a modulus is left unsolved x^2/abs(1 - x^2) = x was answered by a numerical search in the complex codomain, the default, with two non-real entries beside 1.618 that solve nothing: Newton's method assumes a holomorphic function, and its near-copies of a root came back as roots. abs(x^2 - 4) = 3x had 4.0000000000000062 + 1.6e-7 i beside { 1, 4 }. Over the complex numbers |u| = +-u only where u is real, which a root need not make it (x^2 + 1 = |x| has roots at +-0.618 i), so a split by the sign cannot say it found every root either. An equation with x inside abs, sgn, conj, Re, Im or arg and outside it as well is now left unsolved in the complex codomain, at the point where only the search is left, as |x - 2| = |x - 3| already was (#1573). That gives up the cases the search happened to answer right, x * abs(x) = 4 among them; over the reals each is solved by cases, exactly, since #2178. ModulusEquationTest takes six such equations left unsolved, four of which master answered, and two that are still answered. The suite, the trio, F#, AOT and the eight harnesses pass. Fixes #2139. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 15 +++++++++++ .../AnalyticalEquationSolver.cs | 8 ++++++ .../Solvers/EquationSolver/ModulusSolver.cs | 22 ++++++++++++++++ .../Algebra/SolveTest/ModulusEquationTest.cs | 25 +++++++++++++++++++ 4 files changed, 70 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index c83cb36be..73063cab1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1034,6 +1034,21 @@ unchanged ([#2256](https://github.com/asc-community/AngouriMath/issues/2256)). | `"max(x, x)".ToEntity().Simplify()` | `x` | `x provided x in RR` | | `"max(x, 2)".ToEntity().DomainCondition` | `True` | `x in RR` | +### Over the complex numbers, an equation in and out of a modulus is left unsolved + +**Silent: an equation with `x` inside `abs`, `sgn`, `conj`, `Re`, `Im` or `arg` and outside it too is left unsolved +in the complex codomain, the default, where a numerical search answered it.** Newton's method assumes a holomorphic +function, and over the complex numbers `|u| = ±u` only where `u` is real, which a root need not make it, so neither +the search nor a split by the sign finds every root. The search's near-copies of a root came back as roots. Where it +happened to be right, as for `x * abs(x) = 4`, the answer is given up with the rest; over the reals each is still +solved by cases, exactly ([#2139](https://github.com/asc-community/AngouriMath/issues/2139)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"x^2/abs(1-x^2) = x".ToEntity().Solve("x")` | `{ 0, 0.618…, 1.618…, 1.618… + 1.7E-9i, 1.618… + 9.9E-8i }` | `{ x : x ^ 2 / abs(1 - x ^ 2) = x }` | +| `"abs(x^2 - 4) = 3x".ToEntity().Solve("x")` | `{ 1, 4, 4.0000000000000062… + 1.6E-7i }` | `{ x : abs(x ^ 2 - 4) = 3 * x }` | +| `"x * abs(x) = 4".ToEntity().Solve("x")` | `{ 2 }` | `{ x : x * abs(x) = 4 }` | + ### `conjugate`, `conj` and `arg` are functions **Not silent: these names followed by a bracket are now the complex conjugate and the principal argument, where they diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs index c2b46ab42..d2225b5e7 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs @@ -392,6 +392,14 @@ bool ProductIsDefinedAt(Entity root) if (ModulusSolver.IsRealForEveryComplex(expr, x)) return Unsolved(expr, x); + // Over the complex numbers, x inside a modulus or another function that is not + // holomorphic and outside it as well is not the search's either: Newton's method + // assumes a holomorphic function, and its near-copies of a root came back as roots. + // Left unsolved; over the reals it is solved by cases, above. + // https://github.com/asc-community/AngouriMath/issues/2139 + if (MathS.Settings.Codomain.Value != Core.Domain.Real && ModulusSolver.IsInsideAndOutsideAFunctionThatIsNotHolomorphic(expr, x)) + return Unsolved(expr, x); + // A numerical search that comes back with nothing has not shown there is // nothing to find: Newton's method is started from finitely many points inside // a bounded region, so an empty result is a fact about the search rather than diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs index 6de43fa8f..39c13636f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs @@ -37,6 +37,28 @@ internal static class ModulusSolver internal static bool IsRealForEveryComplex(Entity expr, Variable x) => expr.ContainsNode(x) && RealWhereverItIs(expr, x); + /// + /// Whether is both inside a function that is not holomorphic -- a + /// modulus, a signum, a conjugate, a real or imaginary part, an argument -- and outside every + /// such function somewhere in . + /// + /// + /// Newton's method assumes a holomorphic function, and over the complex numbers + /// |u| = +-u only where u is real, which a root need not make it: + /// x^2 + 1 = |x| has roots at +-0.618 i, where |x| is 0.618. So + /// neither the search nor a split by the sign finds every root, and the search's near-copies + /// of a root came back as roots: x^2/|1 - x^2| = x had 1.618 + 1.7e-9 i beside + /// 1.618. https://github.com/asc-community/AngouriMath/issues/2139 + /// + internal static bool IsInsideAndOutsideAFunctionThatIsNotHolomorphic(Entity expr, Variable x) + => expr.Nodes.Any(node => IsNotHolomorphic(node) && node.ContainsNode(x)) && IsOutsideThem(expr, x); + + private static bool IsNotHolomorphic(Entity node) + => node is Absf or Signumf or Conjugatef or Ref or Imf or Argf; + + private static bool IsOutsideThem(Entity expr, Variable x) + => expr == x || !IsNotHolomorphic(expr) && expr.DirectChildren.Any(child => IsOutsideThem(child, x)); + private static bool RealWhereverItIs(Entity expr, Variable x) => expr switch { diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs index 830c5b0f9..abe2c7596 100644 --- a/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs @@ -33,6 +33,31 @@ public sealed class ModulusEquationTest public void OverTheComplexNumbersACurveIsNotAListOfPoints(string equation) => Assert.IsType(equation.ToEntity().Solve("x")); + /// + /// x inside a function that is not holomorphic and outside it too: Newton's method + /// assumes a holomorphic function, and over the complex numbers |u| = +-u only where + /// u is real, which a root need not make it, so neither the search nor a split by the + /// sign finds every root. Left unsolved. The search's near-copies of a root came back as + /// roots: x^2/|1 - x^2| = x had 1.618 + 1.7e-9 i beside 1.618. + /// #2139 + /// + [Theory] + [InlineData("x^2/abs(1 - x^2) = x")] + [InlineData("abs(x^2 - 4) = 3 x")] + [InlineData("x^2 + 1 = abs(x)")] + [InlineData("x * abs(x) = 4")] + [InlineData("x + sgn(x) = 3")] + [InlineData("x + conj(x) = 2 + i")] + public void OverTheComplexNumbersAnEquationInAndOutOfAModulusIsLeftUnsolved(string equation) + => Assert.IsType(equation.ToEntity().Solve("x")); + + /// The variable only inside the modulus, or not in one at all, is answered as before. + [Theory] + [InlineData("abs(x) = 2")] + [InlineData("x^2 = 4")] + public void OverTheComplexNumbersTheOthersAreStillAnswered(string equation) + => Assert.IsNotType(equation.ToEntity().Solve("x")); + /// /// Sullivan and Mackey's Prob 1.5.16, and moduli between which a whole interval solves. ///