Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,20 @@ variable's name ([#2136](https://github.com/asc-community/AngouriMath/issues/213
| `"conj(z)".ToEntity()` | `conj * z` | throws `UnrecognizedFunctionParseException` |
| `"det([[1, 2], [3, 4]])".ToEntity()` | `det * [[1, 2], [3, 4]]` | throws `UnrecognizedFunctionParseException` |

### Over the reals, a modulus of a polynomial is split at the polynomial's real roots

**Silent: an answer with entries that solve nothing is exact.** With the real codomain an equation in moduli was
solved by cases only where every modulus was of a linear function. A modulus of a polynomial fell through to a
numerical search, which answered in decimals and kept non-real numbers near a root that solve nothing: an answer
over the reals with entries that are neither real nor roots. A polynomial keeps one sign between its neighbouring
real roots, so those are now the kinks, and each piece is solved exactly
([#2139](https://github.com/asc-community/AngouriMath/issues/2139)).

| Input, with `MathS.Settings.Codomain` set to `Domain.Real` | Was (2.6.0) | Now |
|---|---|---|
| `"x^2/abs(1-x^2) = x".Solve("x")` | `{ 0, 0.618…, 1.618…, 1.618… + 1.7E-9i, … }` | `{ 0, (-1 + sqrt(5)) / 2, (1 + sqrt(5)) / 2 }` |
| `"abs(x^2 - 4) = 3*x".Solve("x")` | `{ 1, 4, 4.0000000000000062… + 1.6E-7i }` | `{ 1, 4 }` |

### A system with more or fewer equations than unknowns is solved or declined

**Not silent: what was an exception is an answer or `null`.** `EquationSystem.Solve` threw
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,35 @@ private static bool RealWhereverItIs(Entity expr, Variable x)

/// <summary>
/// The real <paramref name="x"/> at which <paramref name="expr"/> is zero, where every
/// modulus in it with <paramref name="x"/> inside is of a linear function with real
/// coefficients; <see langword="null"/> otherwise, or where a piece is not solved.
/// modulus in it with <paramref name="x"/> inside is of a polynomial with real
/// coefficients whose real roots are found; <see langword="null"/> otherwise, or where a
/// piece is not solved.
/// </summary>
/// <remarks>
/// A polynomial keeps one sign between two neighbouring real roots, so its real roots are
/// where a modulus of it can change from the argument to its negation, as a linear one's
/// single root is. <c>x^2/|1 - x^2| = x</c> has kinks at -1 and 1, and on each piece an
/// equation with no modulus, solved exactly; it was answered by a numerical search, in
/// decimals and with two non-real numbers that solve nothing.
/// https://github.com/asc-community/AngouriMath/issues/2139
/// </remarks>
internal static Set? SolveOverTheReals(Entity expr, Variable x)
{
var moduli = new List<Entity>();
var kinks = new List<Real>();
var kinks = new List<(Entity Exact, Real Value)>();
foreach (var node in expr.Nodes)
{
if (node is not Absf(var argument) || !argument.ContainsNode(x))
continue;
if (!TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var intercept)
|| intercept.ContainsNode(x) || slope.Evaled is not Real { IsZero: false }
|| (-intercept / slope).InnerSimplified is not Real kink)
if (RealRootsOf(argument, x) is not { } roots)
return null;
moduli.Add(argument);
kinks.Add(kink);
kinks.AddRange(roots);
}
if (moduli.Count == 0)
return null;
var ends = kinks.Distinct().OrderBy(kink => kink.EDecimal).ToList();
var ends = kinks.GroupBy(kink => kink.Value).Select(same => same.First())
.OrderBy(kink => kink.Value.EDecimal).Select(kink => kink.Exact).ToList();

// The pieces between neighbouring kinks, closed where they end at one, and on each the
// sign every modulus takes, read at a point inside it.
Expand All @@ -81,7 +89,8 @@ private static bool RealWhereverItIs(Entity expr, Variable x)
{
Entity left = i == 0 ? Real.NegativeInfinity : ends[i - 1];
Entity right = i == ends.Count ? Real.PositiveInfinity : ends[i];
Entity inside = i == 0 ? (Entity)ends[0] - 1 : i == ends.Count ? (Entity)ends[^1] + 1 : ((Entity)ends[i - 1] + ends[i]) / 2;
Entity inside = ends.Count == 0 ? Integer.Zero
: i == 0 ? ends[0] - 1 : i == ends.Count ? ends[^1] + 1 : (ends[i - 1] + ends[i]) / 2;
var signs = new Dictionary<Entity, bool>();
foreach (var argument in moduli)
if (!signs.ContainsKey(argument))
Expand Down Expand Up @@ -118,6 +127,39 @@ private static bool RealWhereverItIs(Entity expr, Variable x)
return (Set)answer.InnerSimplified;
}

/// <summary>
/// The real roots of <paramref name="argument"/>, exact and with their values, where it is a
/// polynomial in <paramref name="x"/> with real numeric coefficients whose roots the solver
/// finds; <see langword="null"/> otherwise.
/// </summary>
private static List<(Entity Exact, Real Value)>? RealRootsOf(Entity argument, Variable x)
{
if (TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var intercept))
return !intercept.ContainsNode(x) && slope.Evaled is Real { IsZero: false }
&& (-intercept / slope).InnerSimplified is Real kink
? new() { (kink, kink) }
: null;
if (!TreeAnalyzer.TryGetPolynomial(argument, x, out var monomials)
|| monomials.Count == 0
|| monomials.Keys.Any(power => power.Sign < 0)
|| monomials.Values.Any(coefficient => coefficient.ContainsNode(x) || coefficient.Evaled is not Real))
return null;
if (AnalyticalEquationSolver.Solve(argument, x) is not FiniteSet roots)
return null;
var real = new List<(Entity, Real)>();
foreach (var root in roots)
switch (root.Evaled)
{
case Real value:
real.Add((root, value));
break;
case Complex { ImaginaryPart.IsZero: true } value:
real.Add((root, value.RealPart));
break;
}
return real;
}

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;
Expand Down
19 changes: 19 additions & 0 deletions Sources/Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,24 @@ public void OverTheRealsTheKinksCutItIntoCases(string equation, string expected)
using var _ = MathS.Settings.Codomain.Set(Domain.Real);
Assert.Equal(expected.ToEntity(), equation.ToEntity().Solve("x"));
}

/// <summary>
/// A modulus of a polynomial keeps one sign between the polynomial's neighbouring real
/// roots, so those are its kinks. <c>x^2/|1 - x^2| = x</c> was answered by a numerical
/// search, in decimals and with two non-real numbers that solve nothing.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2139">#2139</a>
/// </summary>
[Theory]
[InlineData("x^2/abs(1 - x^2) = x", "{ 0, (-1 + sqrt(5)) / 2, (1 + sqrt(5)) / 2 }")]
[InlineData("abs(x^2 - 4) = 3 x", "{ 1, 4 }")]
[InlineData("abs(x^2 - 1) + abs(x - 2) = 3", "{ -1, 0, 2 }")]
[InlineData("abs(x^2 - 2) = 1", "{ -sqrt(3), 1, -1, sqrt(3) }")]
[InlineData("abs(x^2 + 1) = 2", "{ 1, -1 }")]
[InlineData("x^2 + 1 = abs(x)", "{ }")]
public void OverTheRealsAPolynomialIsCutAtItsRealRoots(string equation, string expected)
{
using var _ = MathS.Settings.Codomain.Set(Domain.Real);
Assert.Equal(expected.ToEntity(), equation.ToEntity().Solve("x"));
}
}
}
Loading