diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 0af64e36f..2977ffe9a 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -1049,20 +1049,26 @@ 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)).
+### Over the complex numbers, an equation in and out of a modulus is solved in the parts of `x`
+
+**Silent: an equation with `x` inside `abs`, `sgn`, `conj`, `Re` or `Im` and outside it too is solved exactly in the
+complex codomain, the default, where a numerical search answered it in decimals with near-copies of a root that solve
+nothing.** 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 a split by the sign misses roots too: `x^2 + 1 = abs(x)` is solved by
+`±0.618… i`. With `x = a + b i` and each modulus a real `t ≥ 0` with `t^2 = (Re u)^2 + (Im u)^2`, the equation is a
+system of polynomial equations in `a`, `b` and the `t`s, solved by a Gröbner basis, and its real solutions are the
+roots; `sgn(u)` is `u / |u|` where `u` is not zero and `0` where it is, and both cases are solved. Where that system
+has more than finitely many solutions, roots that are not found exactly, or more than 32 by Bézout's bound, the
+equation is left unsolved, as it is with `arg` in it
+([#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 }` |
+| `"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 }` | `{ 0, (1 + sqrt(5)) / 2, (sqrt(5) - 1) / 2 }` |
+| `"abs(x^2 - 4) = 3x".ToEntity().Solve("x")` | `{ 1, 4, 4.0000000000000062… + 1.6E-7i }` | `{ 1, 4 }` |
+| `"1/abs(x) = x^2".ToEntity().Solve("x")` | `{ -1, -0.999999… - 2.1E-7i, -0.999999… + 2.1E-7i, …, 1 }`, eight entries | `{ 1, -1 }` |
+| `"x^2 + 1 = abs(x)".ToEntity().Solve("x")` | `{ x : x ^ 2 + 1 = abs(x) }` | `{ 1/2 * i * (sqrt(5) - 1), 1/2 * i * (1 - sqrt(5)) }` |
+| `"x^5 = abs(x) + 1".ToEntity().Solve("x")` | `{ -0.944368… - 0.686124…i, …, 1.167303… }`, eight entries for five roots | `{ x : x ^ 5 = abs(x) + 1 }` |
### `conjugate`, `conj` and `arg` are functions
diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/MultivariatePolynomial.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/MultivariatePolynomial.cs
index 20bd57858..e4ba14342 100644
--- a/Sources/AngouriMath/Functions/Algebra/Polynomials/MultivariatePolynomial.cs
+++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/MultivariatePolynomial.cs
@@ -147,6 +147,18 @@ internal int DegreeIn(int variable)
return degree;
}
+ /// The highest total degree of a term; zero for a constant.
+ internal int Degree
+ {
+ get
+ {
+ var degree = 0;
+ foreach (var monomial in terms.Keys)
+ degree = System.Math.Max(degree, TotalDegree(monomial));
+ return degree;
+ }
+ }
+
/// The greatest monomial in lexicographic order; zero if there is none.
private ulong LeadingMonomial()
{
diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs
index d2225b5e7..3bc79a3cf 100644
--- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs
@@ -130,6 +130,16 @@ internal static Set Solve(Entity expr, Variable x, bool compensateSolving = fals
if (MathS.Settings.Codomain.Value == Core.Domain.Real && ModulusSolver.SolveOverTheReals(expr, x) is { } overTheReals)
return overTheReals;
+ // Over the complex numbers, one with x inside a modulus, a conjugate, a real or an
+ // imaginary part and outside it as well is solved in the real and imaginary parts of
+ // x, exactly: x |x| = 4 is { 2 }. Asked here, of the equation as it was asked, rather
+ // than of each spelling below, every one of which would be the same system to solve.
+ // https://github.com/asc-community/AngouriMath/issues/2139
+ if (!compensateSolving && MathS.Settings.Codomain.Value != Core.Domain.Real
+ && ModulusSolver.IsInsideAndOutsideAFunctionThatIsNotHolomorphic(expr, x)
+ && ModulusSolver.SolveOverTheComplexNumbers(expr, x) is { } overTheComplexNumbers)
+ return overTheComplexNumbers;
+
// Whether a candidate root really is one. The loose tolerance that guesses the
// rational must not also be what decides this: at 1e-7, x^41 + 6x + 1 accepted
// -1/6, whose residual is -1.25e-32 -- small, but the difference between an
@@ -395,8 +405,8 @@ bool ProductIsDefinedAt(Entity root)
// 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
+ // Left unsolved where it is not solved in the parts of x, above; over the reals it is
+ // solved by cases. https://github.com/asc-community/AngouriMath/issues/2139
if (MathS.Settings.Codomain.Value != Core.Domain.Real && ModulusSolver.IsInsideAndOutsideAFunctionThatIsNotHolomorphic(expr, x))
return Unsolved(expr, x);
diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs
index 39c13636f..767abe0d1 100644
--- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs
@@ -14,8 +14,9 @@
namespace AngouriMath.Functions.Algebra.AnalyticalSolving
{
///
- /// Equations in moduli: where one is real for every complex x, and how one in moduli of
- /// linear functions is solved over the reals.
+ /// Equations in moduli: where one is real for every complex x, how one in moduli of
+ /// polynomials is solved over the reals, and how one in and out of moduli, conjugates, real
+ /// and imaginary parts is solved over the complex numbers.
///
///
/// Over the complex numbers |x - 2| = |x - 3| holds on the whole line
@@ -51,7 +52,27 @@ internal static bool IsRealForEveryComplex(Entity expr, Variable x)
/// 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);
+ => IsInsideOne(expr, x) && IsOutsideThem(expr, x);
+
+ ///
+ /// Whether is inside a function that is not holomorphic somewhere in
+ /// .
+ ///
+ ///
+ /// Walked by hand, since the solver asks it of every equation it is given: enumerating
+ /// allocates an iterator at each level, and that was 4.5 MB of
+ /// the solve the kernel benchmark calls SolveHard, which has no such function in it.
+ ///
+ private static bool IsInsideOne(Entity expr, Variable x)
+ {
+ if (IsNotHolomorphic(expr))
+ return expr.ContainsNode(x);
+ var children = expr.DirectChildren;
+ for (var i = 0; i < children.Count; i++)
+ if (IsInsideOne(children[i], x))
+ return true;
+ return false;
+ }
private static bool IsNotHolomorphic(Entity node)
=> node is Absf or Signumf or Conjugatef or Ref or Imf or Argf;
@@ -185,5 +206,320 @@ private static bool RealWhereverItIs(Entity expr, Variable x)
private static bool Within(Real value, Entity left, Entity right)
=> left.Evaled is Real low && right.Evaled is Real high
&& low.EDecimal.CompareTo(value.EDecimal) <= 0 && value.EDecimal.CompareTo(high.EDecimal) <= 0;
+
+ ///
+ /// The complex at which is zero, where it is
+ /// made by arithmetic and whole powers from , rational numbers, and
+ /// moduli, signums, conjugates, real and imaginary parts of such expressions;
+ /// otherwise, or where the roots are not finitely many or not
+ /// found exactly.
+ ///
+ ///
+ ///
+ /// With x = a + b i for real a and b, and each modulus |u| a
+ /// real t, not negative, with t^2 = (Re u)^2 + (Im u)^2, the real and the
+ /// imaginary part of the equation and one equation for each modulus are polynomial
+ /// equations in as many unknowns, solved exactly by a Groebner basis. The roots are the
+ /// solutions with a, b and every t real and no t negative.
+ /// x |x| = 4 is a t = 4, b t = 0 and t^2 = a^2 + b^2, so
+ /// t = 2 and x = 2; and x^2 + 1 = |x| has the roots +-0.618 i,
+ /// which no split by the sign of x finds.
+ /// https://github.com/asc-community/AngouriMath/issues/2139
+ ///
+ ///
+ /// A signum sgn(u) is u / |u| where u is not zero and 0 where
+ /// it is, so each is solved in both cases: x + sgn(x) = 3 is { 2 } where
+ /// x is not zero, and has no root where it is. An argument is not taken, since it
+ /// is not a polynomial in the parts.
+ ///
+ ///
+ internal static Set? SolveOverTheComplexNumbers(Entity expr, Variable x)
+ {
+ if (expr.Vars.Any(variable => variable != x))
+ return null;
+ var signums = expr.Nodes.OfType().Select(signum => signum.Argument).Distinct().ToList();
+ if (signums.Count > MostSignums)
+ return null;
+ var roots = new List();
+ for (var zero = 0; zero < 1 << signums.Count; zero++)
+ {
+ var zeroed = signums.Where((_, i) => (zero >> i & 1) == 1).ToList();
+ if (RootsWhereZero(expr, x, zeroed) is not { } found)
+ return null;
+ roots.AddRange(found);
+ }
+ return (Set)new FiniteSet(roots).InnerSimplified;
+ }
+
+ /// The most signums an equation solved in the parts of x has, each doubling the systems to solve.
+ private const int MostSignums = 2;
+
+ ///
+ /// The roots of at which every argument of a signum in
+ /// is zero and every other is not; where
+ /// they are not found.
+ ///
+ private static List? RootsWhereZero(Entity expr, Variable x, List zeroed)
+ {
+ var taken = new List { x };
+ var a = Variable.CreateTemp(taken);
+ taken.Add(a);
+ var b = Variable.CreateTemp(taken);
+ taken.Add(b);
+ var splitter = new Splitter(x, a, b, taken);
+ var where = expr.Replace(node => node is Signumf(var argument) && zeroed.Contains(argument) ? Integer.Zero : node);
+ if (splitter.Split(where) is not { } parts)
+ return null;
+ var equations = new List(splitter.Equations) { parts.Real, parts.Imaginary };
+ foreach (var argument in zeroed)
+ {
+ if (splitter.Split(argument) is not { } zero)
+ return null;
+ equations.Add(zero.Real);
+ equations.Add(zero.Imaginary);
+ }
+ var unknowns = new List(splitter.Moduli.Values) { b, a };
+ if (!IsSmallEnoughToSolve(equations, unknowns))
+ return null;
+ if (!Groebner.GroebnerSystemSolver.TrySolve(equations, unknowns, out var solutions))
+ return null;
+ var roots = new List();
+ if (solutions is null)
+ return roots;
+ var ofSignums = new HashSet(splitter.Signums.Select(argument => splitter.Moduli[argument]));
+
+ for (var row = 0; row < solutions.RowCount; row++)
+ {
+ var kept = true;
+ for (var column = 0; column < unknowns.Count; column++)
+ {
+ if (!TryReadReal(solutions[row, column], out var value))
+ return null;
+ // Each unknown is real, and a modulus is not negative, nor zero where it is the
+ // modulus a signum divides by.
+ if (value is null)
+ kept = false;
+ else if (column < unknowns.Count - 2 && (value.IsNegative || value.IsZero && ofSignums.Contains(unknowns[column])))
+ {
+ if (!value.IsZero && !IsClearOfZero(value.EDecimal, value.EDecimal))
+ return null;
+ kept = false;
+ }
+ }
+ if (!kept)
+ continue;
+ var real = solutions[row, unknowns.Count - 1];
+ var imaginary = solutions[row, unknowns.Count - 2];
+ var root = (imaginary.Evaled is Integer { IsZero: true } ? real : real + imaginary * Complex.ImaginaryOne).Simplify();
+ // The system has the equation with its denominators cleared, so a root of it can
+ // be a pole of the equation as written, where it has no value.
+ switch (expr.Substitute(x, root).Evaled)
+ {
+ case Rational { IsZero: true }:
+ case Complex { IsFinite: true } residual when residual is not Rational && Number.IsZero(residual):
+ roots.Add(root);
+ break;
+ case Real { IsNaN: true }:
+ case Complex { IsFinite: false }:
+ break;
+ default:
+ return null;
+ }
+ }
+ return roots;
+ }
+
+ /// The most solutions, counted by Bezout's bound, of a system solved for the parts of x.
+ ///
+ /// The system for x^2/|1 - x^2| = x has degrees 2, 2 and 4, a bound of 16, and is
+ /// solved in a tenth of a second; the one for x^5 = |x| + 1 has 5, 5 and 2, a
+ /// bound of 50, and spent 1.3 seconds before it was declined, and x^7 = |x| + 1
+ /// more than twenty.
+ ///
+ private const int MostSolutions = 32;
+
+ ///
+ /// Whether are polynomials over Q in
+ /// with a product of degrees no more than
+ /// .
+ ///
+ private static bool IsSmallEnoughToSolve(List equations, List unknowns)
+ {
+ var index = new Dictionary();
+ for (var i = 0; i < unknowns.Count; i++)
+ index[unknowns[i]] = i;
+ var bound = 1;
+ foreach (var equation in equations)
+ {
+ if (Functions.MultivariatePolynomial.TryParse(equation, index) is not { } polynomial)
+ return false;
+ if (polynomial.IsZero)
+ continue;
+ bound *= polynomial.Degree;
+ if (bound > MostSolutions)
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Reads as a real number, where it is not
+ /// one; where that is not decided, as where its imaginary part is
+ /// too small to be told from rounding.
+ ///
+ private static bool TryReadReal(Entity value, out Real? real)
+ {
+ real = null;
+ switch (value.Evaled)
+ {
+ case Real { IsFinite: true } number:
+ real = number;
+ return true;
+ case Complex { IsFinite: true } complex when complex.ImaginaryPart.IsZero:
+ real = complex.RealPart;
+ return true;
+ case Complex { IsFinite: true } complex:
+ return IsClearOfZero(complex.ImaginaryPart.EDecimal, complex.RealPart.EDecimal);
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Whether is too large, beside , to be
+ /// what rounding leaves of a zero.
+ ///
+ private static bool IsClearOfZero(PeterO.Numbers.EDecimal part, PeterO.Numbers.EDecimal scale)
+ => part.Abs().CompareTo(PeterO.Numbers.EDecimal.FromString("1E-30").Multiply(PeterO.Numbers.EDecimal.Max(PeterO.Numbers.EDecimal.One, scale.Abs()))) > 0;
+
+ ///
+ /// An expression as (Real + Imaginary i) / Denominator, each of the three real
+ /// wherever the real unknowns are.
+ ///
+ private sealed record Parts(Entity Real, Entity Imaginary, Entity Denominator);
+
+ ///
+ /// Writes an expression in x = a + b i as its , polynomials in
+ /// a, b and one real unknown for each modulus, with the equation that
+ /// defines each in .
+ ///
+ private sealed class Splitter
+ {
+ /// The highest whole power taken apart, past which the parts are too large to solve.
+ private const int HighestPower = 16;
+
+ private readonly Variable x;
+ private readonly Parts ofX;
+ private readonly List taken;
+
+ internal Splitter(Variable x, Variable a, Variable b, List taken)
+ {
+ this.x = x;
+ ofX = new(a, b, Integer.One);
+ this.taken = taken;
+ }
+
+ /// The unknown standing for each modulus, by its argument.
+ internal Dictionary Moduli { get; } = new();
+
+ internal List Equations { get; } = new();
+
+ /// The arguments of the signums, each split as itself over its modulus.
+ internal HashSet Signums { get; } = new();
+
+ internal Parts? Split(Entity expr)
+ => expr switch
+ {
+ _ when expr == x => ofX,
+ Variable => null,
+ Rational => new(expr, Integer.Zero, Integer.One),
+ Complex { RealPart: Rational real, ImaginaryPart: Rational imaginary } => new(real, imaginary, Integer.One),
+ Sumf(var augend, var addend) => Split(augend) is { } left && Split(addend) is { } right ? Add(left, right, Plus) : null,
+ Minusf(var minuend, var subtrahend) => Split(minuend) is { } left && Split(subtrahend) is { } right ? Add(left, right, Less) : null,
+ Mulf(var multiplier, var multiplicand) => Split(multiplier) is { } left && Split(multiplicand) is { } right ? Multiply(left, right) : null,
+ Divf(var dividend, var divisor) => Split(dividend) is { } left && Split(divisor) is { } right ? Divide(left, right) : null,
+ Powf(var @base, Integer exponent) when exponent.EInteger.Abs().CompareTo(HighestPower) <= 0
+ => Split(@base) is { } parts ? Power(parts, exponent.EInteger.ToInt32Checked()) : null,
+ Absf(var argument) => Split(argument) is { } parts ? Modulus(argument, parts) : null,
+ // Where its argument is not zero, which is the case it is split in.
+ Signumf(var argument) => Split(argument) is { } parts ? Divide(parts, Signum(argument, parts)) : null,
+ Conjugatef(var argument) => Split(argument) is { } parts ? new(parts.Real, Less(Integer.Zero, parts.Imaginary), parts.Denominator) : null,
+ Ref(var argument) => Split(argument) is { } parts ? new(parts.Real, Integer.Zero, parts.Denominator) : null,
+ Imf(var argument) => Split(argument) is { } parts ? new(parts.Imaginary, Integer.Zero, parts.Denominator) : null,
+ _ when !expr.ContainsNode(x) && expr.Evaled is Rational value => new(value, Integer.Zero, Integer.One),
+ _ => null,
+ };
+
+ private Parts Signum(Entity argument, Parts parts)
+ {
+ Signums.Add(argument);
+ return Modulus(argument, parts);
+ }
+
+ private Parts Modulus(Entity argument, Parts parts)
+ {
+ if (!Moduli.TryGetValue(argument, out var modulus))
+ {
+ modulus = Variable.CreateTemp(taken);
+ taken.Add(modulus);
+ Moduli[argument] = modulus;
+ Equations.Add(Less(
+ Times(Times(modulus, modulus), Times(parts.Denominator, parts.Denominator)),
+ Plus(Times(parts.Real, parts.Real), Times(parts.Imaginary, parts.Imaginary))));
+ }
+ return new(modulus, Integer.Zero, Integer.One);
+ }
+
+ private static Parts Add(Parts left, Parts right, System.Func add)
+ => left.Denominator == right.Denominator
+ ? new(add(left.Real, right.Real), add(left.Imaginary, right.Imaginary), left.Denominator)
+ : new(
+ add(Times(left.Real, right.Denominator), Times(right.Real, left.Denominator)),
+ add(Times(left.Imaginary, right.Denominator), Times(right.Imaginary, left.Denominator)),
+ Times(left.Denominator, right.Denominator));
+
+ private static Parts Multiply(Parts left, Parts right)
+ => new(
+ Less(Times(left.Real, right.Real), Times(left.Imaginary, right.Imaginary)),
+ Plus(Times(left.Real, right.Imaginary), Times(left.Imaginary, right.Real)),
+ Times(left.Denominator, right.Denominator));
+
+ // (p + q i) / d divided by (r + s i) / e is (p + q i)(r - s i) e / (d (r^2 + s^2)).
+ private static Parts Divide(Parts left, Parts right)
+ => IsZero(right.Imaginary)
+ ? new(Times(left.Real, right.Denominator), Times(left.Imaginary, right.Denominator), Times(left.Denominator, right.Real))
+ : new(
+ Times(Plus(Times(left.Real, right.Real), Times(left.Imaginary, right.Imaginary)), right.Denominator),
+ Times(Less(Times(left.Imaginary, right.Real), Times(left.Real, right.Imaginary)), right.Denominator),
+ Times(left.Denominator, Plus(Times(right.Real, right.Real), Times(right.Imaginary, right.Imaginary))));
+
+ private static Parts Power(Parts parts, int exponent)
+ {
+ Parts result = new(Integer.One, Integer.Zero, Integer.One);
+ var square = parts;
+ for (var remaining = System.Math.Abs(exponent); remaining > 0; remaining >>= 1)
+ {
+ if ((remaining & 1) == 1)
+ result = Multiply(result, square);
+ if (remaining > 1)
+ square = Multiply(square, square);
+ }
+ return exponent < 0 ? Divide(new(Integer.One, Integer.Zero, Integer.One), result) : result;
+ }
+
+ private static bool IsZero(Entity part) => part is Integer { IsZero: true };
+
+ private static Entity Times(Entity left, Entity right)
+ => IsZero(left) || IsZero(right) ? Integer.Zero
+ : left == Integer.One ? right
+ : right == Integer.One ? left
+ : left * right;
+
+ private static Entity Plus(Entity left, Entity right)
+ => IsZero(left) ? right : IsZero(right) ? left : left + right;
+
+ private static Entity Less(Entity left, Entity right)
+ => IsZero(right) ? left : left - right;
+ }
}
}
diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs
index abe2c7596..e1553f5e9 100644
--- a/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs
+++ b/Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs
@@ -34,21 +34,51 @@ 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.
+ /// x inside a modulus, a conjugate, a real or an imaginary part and outside it too,
+ /// solved in the real and imaginary parts of x, exactly. Newton's method assumes a
+ /// holomorphic function, and its 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. Nor does a
+ /// split by the sign find every root, since over the complex numbers |u| = +-u only
+ /// where u is real: x^2 + 1 = |x| is solved by +-0.618 i.
/// #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^2/abs(1 - x^2) = x", "0", "(sqrt(5) - 1) / 2", "(sqrt(5) + 1) / 2")]
+ [InlineData("abs(x^2 - 4) = 3 x", "1", "4")]
+ [InlineData("x * abs(x) = 4", "2")]
+ [InlineData("x^2 + 1 = abs(x)", "i * (sqrt(5) - 1) / 2", "-i * (sqrt(5) - 1) / 2")]
+ [InlineData("x * (abs(x) - x^2) = 0", "0", "1", "-1")]
+ [InlineData("1 / abs(x) = x^2", "1", "-1")]
+ [InlineData("abs(1 / (x - 1)) = x", "(sqrt(5) + 1) / 2")]
+ [InlineData("conj(x) = x^2", "0", "1", "-1/2 + i * sqrt(3) / 2", "-1/2 - i * sqrt(3) / 2")]
+ [InlineData("x + Re(x) = 3 + i", "3/2 + i")]
+ [InlineData("Im(x)^2 + x = 1", "1")]
[InlineData("x + conj(x) = 2 + i")]
- public void OverTheComplexNumbersAnEquationInAndOutOfAModulusIsLeftUnsolved(string equation)
+ [InlineData("abs(x)^2 = x + i")]
+ [InlineData("x + sgn(x) = 3", "2")]
+ [InlineData("x * sgn(x) = 2", "2", "-2")]
+ [InlineData("x^2 = sgn(x)", "0", "1")]
+ [InlineData("sgn(x^2 - 1) = x")]
+ public void OverTheComplexNumbersAnEquationInAndOutOfAModulusIsSolvedInThePartsOfX(string equation, params string[] roots)
+ {
+ var answer = Assert.IsType(equation.ToEntity().Solve("x"));
+ Assert.Equal(roots.Length, answer.Count);
+ foreach (var root in roots)
+ Assert.Contains(answer, found => Entity.Number.IsZero((found - root.ToEntity()).EvalNumerical()));
+ }
+
+ ///
+ /// Left unsolved where the roots are not finitely many, as the ray |x| = Re x; where
+ /// one is not found exactly, as the real root of x^5 - x - 1; where the system is
+ /// too large to solve in the time an answer is worth; and with an argument, which is not a
+ /// polynomial in the parts of x.
+ ///
+ [Theory]
+ [InlineData("abs(x) = Re(x)")]
+ [InlineData("x^5 = abs(x) + 1")]
+ [InlineData("x^7 = abs(x) + 1")]
+ [InlineData("x + arg(x) = 1")]
+ public void OverTheComplexNumbersTheRestIsLeftUnsolved(string equation)
=> Assert.IsType(equation.ToEntity().Solve("x"));
/// The variable only inside the modulus, or not in one at all, is answered as before.