Skip to content

Commit f7f2a7a

Browse files
Hold an answer through a root taken real where a second root makes the integrand real (#2017)
Three places take a root as a nonnegative real u and attach `radicand >= 0` to the answer: the factoring in SolveByLinearRadicalSubstitution and the "as the root" and factored branches of SolveBySubstitution. Where a second root is imaginary on the same side, the integrand is real there as a product of two imaginary roots, and the answer said nothing. Timofeev 926 and 927, asec(x) beside sqrt(x^2 - 1), are real on 0 < x < 1; Rubi's sqrt(cot) (a + b tan)^(k/2) and its kin in 4.3.2.1 and 4.3.3.1 are real where tan < -a/b. ConditionedOnTheRadicand, where the answer holds an even root besides, asks PartialFractions.WhereTheDerivativeHoldsBeyondTheRadicand. That sorts 15 points per sign pattern by interval arithmetic on the integrand and the radicand, and only where one is real beyond the zero differentiates the answer, lazily and real points first. An answer that holds at every real point there, beside exactly one other half-odd root, carries the integrand's real domain -- `tan(c + d x) >= 0 or a + b tan(c + d x) <= 0`; one that also holds at every complex point carries no condition; anything else keeps the old one, and so does an integrand real nowhere beyond the zero. 40 answers on part of the line -> whole (26 of 4.3.3.1, 12 of 4.3.2.1, Timofeev 926, 927), each checked with mpmath on both sides; the condition grows 28-60 characters. Chain against master a567c8b (base a4e7656): pocket of 2,104 rows, exactly those 40 Partial -> Solved, 0 wrong; samples unchanged in verdicts. The check costs 24-111 MB on the rows it changes; Bondarenko 22 and 23, which the first version re-answered at 2.4x the allocation, are master's again, byte for byte, at 0.582 and 0.505 GB. Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent c3b136d commit f7f2a7a

4 files changed

Lines changed: 353 additions & 5 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7331,6 +7331,35 @@ written back is the argument's, which is that of `x` only for a positive slope a
73317331
| `"asec(x)^2/(x*sqrt(x^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | the same |
73327332
| `"asec(x + 3)^2/((x + 3)*sqrt((x + 3)^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | right for `x > -2` and for `x < -4` |
73337333

7334+
### An answer through a root taken real holds where a second root makes the integrand real
7335+
7336+
**Answers where there were none.** An integrand real where a radicand is negative, a second root
7337+
being imaginary there too, was integrated through the root of that radicand as a non-negative real
7338+
`u`, and the answer said `provided` the radicand is not negative: under `u = sqrt(tan(c + d x))` for
7339+
`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
7340+
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
7341+
leaves from Timofeev's `arcsec(x) sqrt(x^2 - 1)/x^4` and `arcsec(x)/(x^2 - 1)^(5/2)`. On the
7342+
unreleased master these said nothing where `tan(c + d x) < -a/b`, or on `0 < x < 1`, though each
7343+
integrand is real there; 2.5.0 declined them. The answer is measured there now, its derivative
7344+
against the integrand at a spread of points where the radicand is negative, the symbols pinned to
7345+
four patterns of signs. These hold where the integrand is real and not where it is imaginary, so
7346+
beside one other half-odd root the condition is that either radicand has its sign,
7347+
`provided tan(c + d x) >= 0 or a + b tan(c + d x) <= 0`, the integrand's real domain; for the
7348+
arcsecants it is `x^2 - 1 >= 0 or (x^2)^2 - x^2 <= 0`, true for every real `x`. An answer that also
7349+
holds at every point measured where the integrand is imaginary carries no condition. One that differs
7350+
where the integrand is real keeps the old one, and so does one whose integrand is real nowhere beyond
7351+
the radicand's zero, which is not measured: Bondarenko's `sqrt(x + sqrt(1 + x))/(1 + x^2)` keeps
7352+
`provided 1 + x >= 0`, though its answer, a sum over the roots of an octic, holds beyond `x = -1` as a
7353+
complex function ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
7354+
7355+
| Input | Was (2.5.0) | Now |
7356+
|---|---|---|
7357+
| `"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` |
7358+
| `"asec(x)/(-1 + x^2)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 506 characters, the same condition twice |
7359+
| `"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` |
7360+
| `"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` |
7361+
| `"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 |
7362+
73347363
### `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`
73357364

73367365
**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)`

‎Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs‎

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,6 +3063,161 @@ internal static bool DerivativeDiffersWhereReal(Entity antiderivative, Entity in
30633063
return DiffersWhereReal(Bare(pinnedAntiderivative).Differentiate(x), pinnedIntegrand, x);
30643064
}
30653065

3066+
/// <summary>
3067+
/// Where the derivative of <paramref name="antiderivative"/> is shown to be
3068+
/// <paramref name="integrand"/> on the part of the line where <paramref name="radicand"/> is
3069+
/// negative: at every point there of a spread along the line, compared as complex numbers,
3070+
/// or at every point there where the integrand is real; at two points or more where it is
3071+
/// real, and nowhere that it is real does it differ or have no value. An answer built
3072+
/// through the root of the radicand as a real <c>u</c> -- a sign of <c>u</c> read as one,
3073+
/// <c>sqrt(u^2 w)</c> as <c>u sqrt(w)</c> -- was conditioned on the radicand for that, and
3074+
/// beside a second root imaginary there the integrand is real there too:
3075+
/// <c>sqrt(a + b t)/t^(3/2)</c> for <c>t &lt; -a/b</c>, a product of two imaginary roots.
3076+
/// Whether the identities still hold there depends on the signs, so it is measured rather
3077+
/// than assumed: <c>(1 + sec(x))^(5/2) sqrt(cos(x))</c> under <c>u = sqrt(cos(x))</c> was the
3078+
/// integrand's negative wherever the cosine is negative, and <c>sqrt(a + b t)/t^(3/2)</c>
3079+
/// under <c>u = sqrt(t)</c> is its antiderivative beyond <c>t = -a/b</c> and not between
3080+
/// <c>-a/b</c> and 0, where the integrand is imaginary.
3081+
/// </summary>
3082+
/// <remarks>
3083+
/// <para>
3084+
/// The other symbols are pinned as <see cref="DerivativeHoldsAtSampledPoints"/> pins them, and
3085+
/// again with each of three patterns of signs, since the region and the identities both move
3086+
/// with them. A pattern counts only where the answer holds at a point where the radicand is
3087+
/// positive: an answer that is not one for those signs at all says nothing of this region.
3088+
/// </para>
3089+
/// <para>
3090+
/// Nothing is differentiated where the integrand is real at no point beyond the radicand's
3091+
/// zero: the condition is then the integrand's real domain already, or more, and measuring
3092+
/// the answer off it buys nothing on the real line. Bondarenko's
3093+
/// <c>sqrt(x + sqrt(1 + x))/(1 + x^2)</c> is such a one, its answer a sum over the roots of
3094+
/// an octic, and measured at the fifteen points it cost twice the integral's allocation to
3095+
/// show it holds beyond <c>x = -1</c> as a complex function. Off the real line the points are
3096+
/// compared until one differs.
3097+
/// </para>
3098+
/// </remarks>
3099+
internal static BeyondTheRadicand WhereTheDerivativeHoldsBeyondTheRadicand(Entity antiderivative, Entity integrand, Entity radicand, Variable x)
3100+
{
3101+
// Differentiated and evaluated at fifteen points for each of four patterns, an answer of
3102+
// a hundred thousand characters is a search of its own; it keeps its condition.
3103+
if (antiderivative.Complexity > LargestAnswerMeasuredBeyondTheRadicand)
3104+
return BeyondTheRadicand.NotShown;
3105+
using var _ = MathS.Settings.DowncastingEnabled.Set(false);
3106+
var parameters = antiderivative.Vars.Concat(integrand.Vars).Concat(radicand.Vars).Where(v => v != x).Distinct().ToList();
3107+
// Each pattern once for the symbols there are: none, and the four are one. The points of
3108+
// each sorted first, which asks the integrand and the radicand alone.
3109+
var measured = new List<(Entity Antiderivative, Entity Integrand, List<Entity> Before, List<Entity> RealBeyond, List<Entity> ComplexBeyond)>();
3110+
foreach (var pattern in SignPatterns.Where(pattern => parameters.Count > 1 || pattern == 0 || parameters.Count == 1 && pattern == 1))
3111+
{
3112+
var (pinnedAntiderivative, pinnedIntegrand, pinnedRadicand) = (Bare(antiderivative), integrand, radicand);
3113+
for (var k = 0; k < parameters.Count; k++)
3114+
{
3115+
var fraction = (k % 3) switch { 0 => "1.37", 1 => "2.71", _ => "0.83" };
3116+
var magnitude = EDecimal.FromString(fraction).Add(EDecimal.FromInt32(k));
3117+
var negative = pattern switch { 1 => k % 2 == 0, 2 => k % 2 == 1, 3 => true, _ => false };
3118+
Entity value = Real.Create(negative ? magnitude.Negate() : magnitude);
3119+
pinnedAntiderivative = pinnedAntiderivative.Substitute(parameters[k], value);
3120+
pinnedIntegrand = pinnedIntegrand.Substitute(parameters[k], value);
3121+
pinnedRadicand = pinnedRadicand.Substitute(parameters[k], value);
3122+
}
3123+
// In double intervals, which are cheap beside the decimals: a point either cannot tell
3124+
// is passed over.
3125+
var (before, realBeyond, complexBeyond) = (new List<Entity>(), new List<Entity>(), new List<Entity>());
3126+
foreach (var at in AlongTheLine)
3127+
{
3128+
var point = Real.Create(EDecimal.FromString(at));
3129+
if (IntervalEvaluation.Of(pinnedIntegrand.Substitute(x, point)) is not { IsFinite: true } value
3130+
|| IntervalEvaluation.Of(pinnedRadicand.Substitute(x, point)) is not { IsFinite: true, IsReal: true } radicandValue
3131+
|| radicandValue.Re.ContainsZero)
3132+
continue;
3133+
var isReal = value.Im.Magnitude <= 1e-9 * System.Math.Max(1, value.Magnitude);
3134+
if (radicandValue.Re.Low > 0)
3135+
{
3136+
if (isReal)
3137+
before.Add(point);
3138+
}
3139+
else
3140+
(isReal ? realBeyond : complexBeyond).Add(point);
3141+
}
3142+
measured.Add((pinnedAntiderivative, pinnedIntegrand, before, realBeyond, complexBeyond));
3143+
}
3144+
if (!measured.Any(pattern => pattern.RealBeyond.Count > 0))
3145+
return BeyondTheRadicand.NotShown;
3146+
3147+
var inDecimals = new System.Lazy<PreciseEvaluation>[] { new(() => PreciseEvaluation.In(40)), new(() => PreciseEvaluation.In(80)) };
3148+
// The derivative of a pattern's answer, where it holds at a point before the zero.
3149+
var derivatives = new Entity?[measured.Count];
3150+
var controlled = new bool[measured.Count];
3151+
Entity? Controlled(int i)
3152+
{
3153+
if (controlled[i])
3154+
return derivatives[i];
3155+
controlled[i] = true;
3156+
var (pinnedAntiderivative, pinnedIntegrand, before, _, _) = measured[i];
3157+
var derivative = Bare(pinnedAntiderivative).Differentiate(x);
3158+
return derivatives[i] = before.Any(point => Agree(derivative.Substitute(x, point), pinnedIntegrand.Substitute(x, point), 1e-9, inDecimals) is true)
3159+
? derivative : null;
3160+
}
3161+
var real = 0;
3162+
for (var i = 0; i < measured.Count; i++)
3163+
{
3164+
if (measured[i].RealBeyond.Count == 0 || Controlled(i) is not { } derivative)
3165+
continue;
3166+
foreach (var point in measured[i].RealBeyond)
3167+
{
3168+
if (Agree(derivative.Substitute(x, point), measured[i].Integrand.Substitute(x, point), 1e-9, inDecimals) is not true)
3169+
return BeyondTheRadicand.NotShown;
3170+
real++;
3171+
}
3172+
}
3173+
if (real < 2)
3174+
return BeyondTheRadicand.NotShown;
3175+
for (var i = 0; i < measured.Count; i++)
3176+
{
3177+
if (measured[i].ComplexBeyond.Count == 0 || Controlled(i) is not { } derivative)
3178+
continue;
3179+
foreach (var point in measured[i].ComplexBeyond)
3180+
if (Agree(derivative.Substitute(x, point), measured[i].Integrand.Substitute(x, point), 1e-9, inDecimals) is not true)
3181+
return BeyondTheRadicand.WhereTheIntegrandIsReal;
3182+
}
3183+
return BeyondTheRadicand.Everywhere;
3184+
}
3185+
3186+
/// <summary>
3187+
/// What <see cref="WhereTheDerivativeHoldsBeyondTheRadicand"/> shows of an answer where the
3188+
/// radicand is negative.
3189+
/// </summary>
3190+
internal enum BeyondTheRadicand
3191+
{
3192+
/// <summary>Nothing: it differs where the integrand is real, too few points compared, or the integrand is real at none beyond the zero.</summary>
3193+
NotShown,
3194+
/// <summary>It holds where the integrand is real, and differs somewhere it is not.</summary>
3195+
WhereTheIntegrandIsReal,
3196+
/// <summary>It holds at every point compared, the integrand real there or not.</summary>
3197+
Everywhere,
3198+
}
3199+
3200+
/// <summary>
3201+
/// The largest answer, by <see cref="Entity.Complexity"/>, that
3202+
/// <see cref="WhereTheDerivativeHoldsBeyondTheRadicand"/> measures; the answers it was
3203+
/// written for are a few thousand characters.
3204+
/// </summary>
3205+
private const int LargestAnswerMeasuredBeyondTheRadicand = 20000;
3206+
3207+
/// <summary>
3208+
/// The signs of the pinned symbols: all positive, alternating either way, all negative.
3209+
/// </summary>
3210+
[ConstantField]
3211+
private static readonly int[] SignPatterns = { 0, 1, 2, 3 };
3212+
3213+
/// <summary>
3214+
/// A spread of points for <see cref="WhereTheDerivativeHoldsBeyondTheRadicand"/>, on both
3215+
/// sides of zero and of one, near and far.
3216+
/// </summary>
3217+
[ConstantField]
3218+
private static readonly string[] AlongTheLine =
3219+
{ "-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" };
3220+
30663221
/// <summary>
30673222
/// Whether <paramref name="antiderivative"/> has no value at any of the points
30683223
/// <see cref="HoldsAtSampledPoints"/> takes by default where <paramref name="integrand"/> has

0 commit comments

Comments
 (0)