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. ///