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 @@ -480,6 +480,20 @@ whatever `a` is ([#718](https://github.com/asc-community/AngouriMath/issues/718)
| `"sqrt(a + i*a*sinh(g + f*x))/x".ToEntity().Integrate("x")` | `integral(...)` | `Shi` and `Chi` of half of `f x` |
| `"1/sqrt(a + i*a*sinh(g + f*x))".ToEntity().Integrate("x")` | `integral(...)` | arctangents and logarithms of `e^((g + f x)/2)` |

### The arm for a quadratic off the real line excludes the arms on the sign of its discriminant

**Shorter answers.** An antiderivative over a quadratic with `i` among its coefficients has an arm
off the real line, `not D in RR or D > 0`, beside the arms `D = 0` and `D < 0`. A sum of two such
answers pairs their arms and drops a pair whose conditions contradict each other, and that arm was
read against neither, though `D = 0` asks `D` to be real and `D < 0` is NaN off the real line. Those
pairs are dropped now
([#1788](https://github.com/asc-community/AngouriMath/issues/1788)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^2/(x^2 + a + i*b)^3".ToEntity().Integrate("x")` | 10 arms, and no value at a real `x`; 7 arms and 2,722 characters on the unreleased master | 3 arms, 1,069 characters |
| `"1/(x^2 + i*a)^2 + 1/(x^2 + i*a)^3".ToEntity().Integrate("x")` | 9 arms, and no value at a real `x`; 7 arms and 1,994 characters on the unreleased master | 3 arms, 779 characters |

### The hyperbolic functions have antiderivatives, and so does anything rational in `e^(k x)`

An integrand rational in `e^(k x)` becomes a rational function of one variable under
Expand Down
95 changes: 86 additions & 9 deletions Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,28 +241,103 @@ private Entity CombinedCaseByCase(Piecewise a, Piecewise b,
private static (bool Always, Entity? Undecided) Contradiction(Entity predicate)
{
var tests = new List<(Entity Quantity, int Relation)>();
var choices = new List<Entity>();
foreach (var conjunct in Conjuncts(predicate))
if (SignTest(conjunct) is { } test)
tests.Add(test);
else if (conjunct is Orf)
choices.Add(conjunct);
Entity? undecided = null;
for (var i = 0; i < tests.Count; i++)
for (var j = i + 1; j < tests.Count; j++)
switch (Against(tests[i], tests[j]))
{
case (true, _):
return (true, null);
case (false, { } quantity):
undecided = quantity;
break;
}
// A disjunction beside them contradicts them where each of its arms does: the arm a
// quadratic off the real line owes, `not D in RR or D > 0`, against `D = 0` is false
// for every value, and against `D < 0` false for a real one and NaN off the real
// line. Read as neither, the sum of the antiderivatives over three powers of one
// quadratic kept every pairing of their arms, 45 for
// `2 d^3 t^2/((t^2 - c - i d)^3 (t^2 - c + i d))` where 9 hold every case.
// https://github.com/asc-community/AngouriMath/issues/1788
foreach (var choice in choices)
{
var (always, quantity, read) = (true, (Entity?)null, true);
foreach (var arm in Disjuncts(choice))
{
if (tests[i].Quantity != tests[j].Quantity)
if (SignTest(arm) is not { } armTest)
{
read = false;
break;
}
var (armAlways, armQuantity) = (false, (Entity?)null);
foreach (var test in tests)
switch (Against(armTest, test))
{
case (true, _):
armAlways = true;
break;
case (false, { } q):
armQuantity ??= q;
break;
}
if (armAlways)
continue;
var (r1, r2) = (tests[i].Relation, tests[j].Relation);
// 0 is "= 0", 2 is "not = 0", 1 and -1 the signs.
if (r1 == 0 && r2 != 0 || r2 == 0 && r1 != 0)
return (true, null);
if (r1 * r2 == -1)
undecided = tests[i].Quantity;
if (armQuantity is null || quantity is not null && quantity != armQuantity)
{
read = false;
break;
}
(always, quantity) = (false, armQuantity);
}
if (!read)
continue;
if (always)
return (true, null);
undecided = quantity;
}
return (false, undecided);
}

/// <summary>
/// Two tests of one quantity <c>q</c>: in <c>Always</c> where no value satisfies both,
/// and otherwise the quantity where a real one satisfies neither together and one off the
/// real line makes a sign NaN; null where they do not test one quantity or agree.
/// </summary>
private static (bool Always, Entity? Undecided) Against((Entity Quantity, int Relation) first, (Entity Quantity, int Relation) second)
{
if (first.Quantity != second.Quantity)
return (false, null);
var (r1, r2) = (first.Relation, second.Relation);
// 0 is "= 0", 2 is "not = 0", 1 and -1 the signs, 3 "not in RR": zero is real.
if (r1 == 0 && r2 != 0 || r2 == 0 && r1 != 0)
return (true, null);
if (r1 * r2 == -1 || r1 == 3 && r2 is 1 or -1 || r2 == 3 && r1 is 1 or -1)
return (false, first.Quantity);
return (false, null);
}

private static IEnumerable<Entity> Disjuncts(Entity condition)
{
if (condition is Orf(var left, var right))
{
foreach (var disjunct in Disjuncts(left))
yield return disjunct;
foreach (var disjunct in Disjuncts(right))
yield return disjunct;
}
else
yield return condition;
}

/// <summary>Whether <paramref name="predicate"/> has a conjunct testing <paramref name="quantity"/> against zero.</summary>
private static bool TestsTheSignOf(Entity predicate, Entity quantity)
=> Conjuncts(predicate).Any(conjunct => SignTest(conjunct) is var (q, relation) && q == quantity && relation != 2);
=> Conjuncts(predicate).Any(conjunct => SignTest(conjunct) is var (q, relation) && q == quantity && relation is 0 or 1 or -1);

/// <summary>
/// A comparison of a quantity with zero as the quantity, with any constant factor taken
Expand All @@ -281,11 +356,13 @@ private static (Entity Quantity, int Relation)? SignTest(Entity conjunct)
Lessf(var zero, var q) when zero == Integer.Zero => (q, 1),
Lessf(var q, var zero) when zero == Integer.Zero => (q, -1),
Greaterf(var zero, var q) when zero == Integer.Zero => (q, -1),
Notf(Inf(var q, SpecialSet.Reals)) => (q, 3),
_ => null,
};
if (read is not var (quantity, relation))
return null;
// The constant factor out: `4 f (-d)` is `f (-d)`, and `2 f` is `f`.
// The constant factor out: `4 f (-d)` is `f (-d)`, and `2 f` is `f`. A real one, which
// leaves `not in RR` as it is.
if (quantity is Mulf(Real factor, var rest) && !factor.IsZero)
(quantity, relation) = (rest, relation is 1 or -1 && factor.IsNegative ? -relation : relation);
return (quantity, relation);
Expand Down
27 changes: 27 additions & 0 deletions Sources/Tests/UnitTests/Core/ContradictoryConjunctionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,33 @@ public void TwoPiecewisesOnTheSameSplitCombineCaseByCase()
Assert.Equal(Number.Integer.Create(11 + 6), chain.Substitute("q", 0).Evaled);
}

/// <summary>
/// The arm an antiderivative owes a quadratic off the real line, <c>not q in RR or q &gt; 0</c>,
/// against the arms on the sign of <c>q</c>: an equality asks <c>q</c> to be real, and a
/// strict sign is NaN off the real line, so neither pairing is ever true, and a sum of
/// such piecewises stays at three cases. The antiderivative of
/// <c>2 d^3 t^2/((t^2 - c - i d)^3 (t^2 - c + i d))</c> kept 45.
/// https://github.com/asc-community/AngouriMath/issues/1788
/// </summary>
[Fact]
public void TheArmOffTheRealLineExcludesTheSignArms()
{
const string arms = "piecewise(1 provided not q in RR or q > 0, 2 provided q = 0, 3 provided q < 0)";
var sum = (arms.ToEntity() + arms.ToEntity()).InnerSimplified;
Assert.Equal(3, CaseCount(sum));
Assert.Equal(Number.Integer.Create(2), sum.Substitute("q", 1).Evaled);
Assert.Equal(Number.Integer.Create(4), sum.Substitute("q", 0).Evaled);
Assert.Equal(Number.Integer.Create(6), sum.Substitute("q", -1).Evaled);
Assert.Equal(Number.Integer.Create(2), sum.Substitute("q", "i").Evaled);

var chain = sum;
for (var i = 0; i < 4; i++)
chain = (chain + arms.ToEntity()).InnerSimplified;
Assert.Equal(3, CaseCount(chain));
Assert.Equal(Number.Integer.Create(6), chain.Substitute("q", "1 + i").Evaled);
Assert.Equal(Number.Integer.Create(18), chain.Substitute("q", -2).Evaled);
}

/// <summary>
/// A quantity is read up to a constant factor: <c>f = 0</c> and <c>not 2 f = 0</c>
/// contradict each other, which is how the integrator's case for a vanishing leading
Expand Down
Loading