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
29 changes: 29 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7316,6 +7316,35 @@ written back is the argument's, which is that of `x` only for a positive slope a
| `"asec(x)^2/(x*sqrt(x^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"asec(x + 3)^2/((x + 3)*sqrt((x + 3)^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | right for `x > -2` and for `x < -4` |

### An answer through a root taken real holds where a second root makes the integrand real

**Answers where there were none.** An integrand real where a radicand is negative, a second root
being imaginary there too, was integrated through the root of that radicand as a non-negative real
`u`, and the answer said `provided` the radicand is not negative: under `u = sqrt(tan(c + d x))` for
`sqrt(cot(c + d x)) (a + b tan(c + d x))^(5/2) (A + B tan(c + d x))`, twenty-five more of Rubi's
4.3.3.1 and twelve of its 4.3.2.1, and under `u = sqrt(w - 1)`, `w = x^2`, for the remainder by parts
leaves from Timofeev's `arcsec(x) sqrt(x^2 - 1)/x^4` and `arcsec(x)/(x^2 - 1)^(5/2)`. On the
unreleased master these said nothing where `tan(c + d x) < -a/b`, or on `0 < x < 1`, though each
integrand is real there; 2.5.0 declined them. The answer is measured there now, its derivative
against the integrand at a spread of points where the radicand is negative, the symbols pinned to
four patterns of signs. These hold where the integrand is real and not where it is imaginary, so
beside one other half-odd root the condition is that either radicand has its sign,
`provided tan(c + d x) >= 0 or a + b tan(c + d x) <= 0`, the integrand's real domain; for the
arcsecants it is `x^2 - 1 >= 0 or (x^2)^2 - x^2 <= 0`, true for every real `x`. An answer that also
holds at every point measured where the integrand is imaginary carries no condition. One that differs
where the integrand is real keeps the old one, and so does one whose integrand is real nowhere beyond
the radicand's zero, which is not measured: Bondarenko's `sqrt(x + sqrt(1 + x))/(1 + x^2)` keeps
`provided 1 + x >= 0`, though its answer, a sum over the roots of an octic, holds beyond `x = -1` as a
complex function ([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"asec(x)*sqrt(-1 + x^2)/x^4".ToEntity().Integrate("x")` | `integral(...)` | 212 characters, `provided x^2 - 1 >= 0 or (x^2)^2 - x^2 <= 0` |
| `"asec(x)/(-1 + x^2)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 506 characters, the same condition twice |
| `"sqrt(cot(c + d*x))*sqrt(a + b*tan(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 523 characters, `provided tan(c + d x) >= 0 or a + b tan(c + d x) <= 0` |
| `"sqrt(a + b*tan(c + d*x))*(A + B*tan(c + d*x))/tan(c + d*x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,067 characters, the same and `not a = 0` |
| `"sqrt(cot(c + d*x))*(a + b*tan(c + d*x))^(5/2)*(A + B*tan(c + d*x))".ToEntity().Integrate("x")` | past twenty seconds | 2,767 characters, the same |

### `x^(4k - 1) (a + b arcsec(c x))/sqrt(1 - c^4 x^4)` and the arccosecant's are integrated on both sides of `|c x| = 1`

**Answers where there were none.** Rubi's 5.5.1 and 5.6.1 `x^(4k - 1) (a + b arcsec(c x))/sqrt(1 - c^4 x^4)`
Expand Down
155 changes: 155 additions & 0 deletions Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3063,6 +3063,161 @@ internal static bool DerivativeDiffersWhereReal(Entity antiderivative, Entity in
return DiffersWhereReal(Bare(pinnedAntiderivative).Differentiate(x), pinnedIntegrand, x);
}

/// <summary>
/// Where the derivative of <paramref name="antiderivative"/> is shown to be
/// <paramref name="integrand"/> on the part of the line where <paramref name="radicand"/> is
/// negative: at every point there of a spread along the line, compared as complex numbers,
/// or at every point there where the integrand is real; at two points or more where it is
/// real, and nowhere that it is real does it differ or have no value. An answer built
/// through the root of the radicand as a real <c>u</c> -- a sign of <c>u</c> read as one,
/// <c>sqrt(u^2 w)</c> as <c>u sqrt(w)</c> -- was conditioned on the radicand for that, and
/// beside a second root imaginary there the integrand is real there too:
/// <c>sqrt(a + b t)/t^(3/2)</c> for <c>t &lt; -a/b</c>, a product of two imaginary roots.
/// Whether the identities still hold there depends on the signs, so it is measured rather
/// than assumed: <c>(1 + sec(x))^(5/2) sqrt(cos(x))</c> under <c>u = sqrt(cos(x))</c> was the
/// integrand's negative wherever the cosine is negative, and <c>sqrt(a + b t)/t^(3/2)</c>
/// under <c>u = sqrt(t)</c> is its antiderivative beyond <c>t = -a/b</c> and not between
/// <c>-a/b</c> and 0, where the integrand is imaginary.
/// </summary>
/// <remarks>
/// <para>
/// The other symbols are pinned as <see cref="DerivativeHoldsAtSampledPoints"/> pins them, and
/// again with each of three patterns of signs, since the region and the identities both move
/// with them. A pattern counts only where the answer holds at a point where the radicand is
/// positive: an answer that is not one for those signs at all says nothing of this region.
/// </para>
/// <para>
/// Nothing is differentiated where the integrand is real at no point beyond the radicand's
/// zero: the condition is then the integrand's real domain already, or more, and measuring
/// the answer off it buys nothing on the real line. Bondarenko's
/// <c>sqrt(x + sqrt(1 + x))/(1 + x^2)</c> is such a one, its answer a sum over the roots of
/// an octic, and measured at the fifteen points it cost twice the integral's allocation to
/// show it holds beyond <c>x = -1</c> as a complex function. Off the real line the points are
/// compared until one differs.
/// </para>
/// </remarks>
internal static BeyondTheRadicand WhereTheDerivativeHoldsBeyondTheRadicand(Entity antiderivative, Entity integrand, Entity radicand, Variable x)
{
// Differentiated and evaluated at fifteen points for each of four patterns, an answer of
// a hundred thousand characters is a search of its own; it keeps its condition.
if (antiderivative.Complexity > LargestAnswerMeasuredBeyondTheRadicand)
return BeyondTheRadicand.NotShown;
using var _ = MathS.Settings.DowncastingEnabled.Set(false);
var parameters = antiderivative.Vars.Concat(integrand.Vars).Concat(radicand.Vars).Where(v => v != x).Distinct().ToList();
// Each pattern once for the symbols there are: none, and the four are one. The points of
// each sorted first, which asks the integrand and the radicand alone.
var measured = new List<(Entity Antiderivative, Entity Integrand, List<Entity> Before, List<Entity> RealBeyond, List<Entity> ComplexBeyond)>();
foreach (var pattern in SignPatterns.Where(pattern => parameters.Count > 1 || pattern == 0 || parameters.Count == 1 && pattern == 1))
{
var (pinnedAntiderivative, pinnedIntegrand, pinnedRadicand) = (Bare(antiderivative), integrand, radicand);
for (var k = 0; k < parameters.Count; k++)
{
var fraction = (k % 3) switch { 0 => "1.37", 1 => "2.71", _ => "0.83" };
var magnitude = EDecimal.FromString(fraction).Add(EDecimal.FromInt32(k));
var negative = pattern switch { 1 => k % 2 == 0, 2 => k % 2 == 1, 3 => true, _ => false };
Entity value = Real.Create(negative ? magnitude.Negate() : magnitude);
pinnedAntiderivative = pinnedAntiderivative.Substitute(parameters[k], value);
pinnedIntegrand = pinnedIntegrand.Substitute(parameters[k], value);
pinnedRadicand = pinnedRadicand.Substitute(parameters[k], value);
}
// In double intervals, which are cheap beside the decimals: a point either cannot tell
// is passed over.
var (before, realBeyond, complexBeyond) = (new List<Entity>(), new List<Entity>(), new List<Entity>());
foreach (var at in AlongTheLine)
{
var point = Real.Create(EDecimal.FromString(at));
if (IntervalEvaluation.Of(pinnedIntegrand.Substitute(x, point)) is not { IsFinite: true } value
|| IntervalEvaluation.Of(pinnedRadicand.Substitute(x, point)) is not { IsFinite: true, IsReal: true } radicandValue
|| radicandValue.Re.ContainsZero)
continue;
var isReal = value.Im.Magnitude <= 1e-9 * System.Math.Max(1, value.Magnitude);
if (radicandValue.Re.Low > 0)
{
if (isReal)
before.Add(point);
}
else
(isReal ? realBeyond : complexBeyond).Add(point);
}
measured.Add((pinnedAntiderivative, pinnedIntegrand, before, realBeyond, complexBeyond));
}
if (!measured.Any(pattern => pattern.RealBeyond.Count > 0))
return BeyondTheRadicand.NotShown;

var inDecimals = new System.Lazy<PreciseEvaluation>[] { new(() => PreciseEvaluation.In(40)), new(() => PreciseEvaluation.In(80)) };
// The derivative of a pattern's answer, where it holds at a point before the zero.
var derivatives = new Entity?[measured.Count];
var controlled = new bool[measured.Count];
Entity? Controlled(int i)
{
if (controlled[i])
return derivatives[i];
controlled[i] = true;
var (pinnedAntiderivative, pinnedIntegrand, before, _, _) = measured[i];
var derivative = Bare(pinnedAntiderivative).Differentiate(x);
return derivatives[i] = before.Any(point => Agree(derivative.Substitute(x, point), pinnedIntegrand.Substitute(x, point), 1e-9, inDecimals) is true)
? derivative : null;
}
var real = 0;
for (var i = 0; i < measured.Count; i++)
{
if (measured[i].RealBeyond.Count == 0 || Controlled(i) is not { } derivative)
continue;
foreach (var point in measured[i].RealBeyond)
{
if (Agree(derivative.Substitute(x, point), measured[i].Integrand.Substitute(x, point), 1e-9, inDecimals) is not true)
return BeyondTheRadicand.NotShown;
real++;
}
}
if (real < 2)
return BeyondTheRadicand.NotShown;
for (var i = 0; i < measured.Count; i++)
{
if (measured[i].ComplexBeyond.Count == 0 || Controlled(i) is not { } derivative)
continue;
foreach (var point in measured[i].ComplexBeyond)
if (Agree(derivative.Substitute(x, point), measured[i].Integrand.Substitute(x, point), 1e-9, inDecimals) is not true)
return BeyondTheRadicand.WhereTheIntegrandIsReal;
}
return BeyondTheRadicand.Everywhere;
}

/// <summary>
/// What <see cref="WhereTheDerivativeHoldsBeyondTheRadicand"/> shows of an answer where the
/// radicand is negative.
/// </summary>
internal enum BeyondTheRadicand
{
/// <summary>Nothing: it differs where the integrand is real, too few points compared, or the integrand is real at none beyond the zero.</summary>
NotShown,
/// <summary>It holds where the integrand is real, and differs somewhere it is not.</summary>
WhereTheIntegrandIsReal,
/// <summary>It holds at every point compared, the integrand real there or not.</summary>
Everywhere,
}

/// <summary>
/// The largest answer, by <see cref="Entity.Complexity"/>, that
/// <see cref="WhereTheDerivativeHoldsBeyondTheRadicand"/> measures; the answers it was
/// written for are a few thousand characters.
/// </summary>
private const int LargestAnswerMeasuredBeyondTheRadicand = 20000;

/// <summary>
/// The signs of the pinned symbols: all positive, alternating either way, all negative.
/// </summary>
[ConstantField]
private static readonly int[] SignPatterns = { 0, 1, 2, 3 };

/// <summary>
/// A spread of points for <see cref="WhereTheDerivativeHoldsBeyondTheRadicand"/>, on both
/// sides of zero and of one, near and far.
/// </summary>
[ConstantField]
private static readonly string[] AlongTheLine =
{ "-7.3", "-3.1", "-1.61", "-0.83", "-0.47", "-0.19", "0.13", "0.29", "0.53", "0.77", "0.94", "1.43", "2.3", "3.17", "6.1" };

/// <summary>
/// Whether <paramref name="antiderivative"/> has no value at any of the points
/// <see cref="HoldsAtSampledPoints"/> takes by default where <paramref name="integrand"/> has
Expand Down
Loading
Loading