Skip to content

Commit a567c8b

Browse files
Answer the borderline rows inside the budget by four guards on slow declines (#2004)
- The pair rule SolveAPairOfHyperbolicPowersTwoApart is asked for sums just before the first sum split, which spent 9-12 s declining each elliptic term before the pair rule answered at its later position. - Exact pre-checks read before the expensive numerator in five readers: a whole power >= 2 of something in x below the bar, a root below the bar, an arctangent present. - SolveByCombiningRadicals counts the root inside the logarithm of an inverse hyperbolic function as no root, where it had rewritten ln(1/u + sqrt(1 + 1/u^2)) into a spelling nothing reads. - The substitution search's repeat test covers any function of one trigonometric function of a linear, a quadratic mirrored across its vertex, and even powers of a quotient of linears; its two values are compared in doubles, and nothing is refused where a double cannot hold them, and it is skipped past a power of x of 64 -- an exact subtraction of two 1,900-digit rationals had hung 1.1.4.2:487. Rubi's 7.6.2:23 goes from 34.8 s and 39.5 GB to 0.6 s, 6.6.1:24-26 and 6.1.1:123-125 from about 23 s to 0.1 s, and 7.2.4:599, 3.2.1:292, 4.7.7:117, 1.2.1.4:1120, 1.2.3.4:111 and 4.1.2.1:659 to 5-11 s. Every answer is unchanged, character for character, across 710 rows; over 300 rows of the 2-12 s band CPU falls 18.6% and allocation 19.0%. 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 1286620 commit a567c8b

3 files changed

Lines changed: 281 additions & 33 deletions

File tree

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 191 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5069,6 +5069,19 @@ bool IsAnExponential(Entity factor)
50695069
return null;
50705070
}
50715071

5072+
/// <summary>
5073+
/// Whether a factor of <paramref name="denominator"/> is a whole power past the first of
5074+
/// something in <paramref name="x"/>: what a rule for a power of a quadratic below the
5075+
/// bar needs, asked before the numerator is read. Under a substitution the numerator can
5076+
/// be a sum of exponentials brought over one bar, thousands of terms, and three such
5077+
/// rules expanded it in turn to be told it was not a polynomial -- six seconds each of
5078+
/// the inverse hyperbolic substitution for `x^4 (d + e x^2)^3 (a + b arcosh(c x))`, whose
5079+
/// denominator holds no x at all. https://github.com/asc-community/AngouriMath/issues/233
5080+
/// </summary>
5081+
private static bool HasAWholePowerPastTheFirstOf(Entity denominator, Entity.Variable x)
5082+
=> Mulf.LinearChildren(denominator).Any(factor =>
5083+
factor is Powf(var @base, Number.Integer { EInteger: var whole }) && whole.CompareTo(EInteger.FromInt32(2)) >= 0 && @base.ContainsNode(x));
5084+
50725085
/// <summary>
50735086
/// Whether <paramref name="expr"/> is built from <paramref name="x"/> by the four
50745087
/// operations and rational powers alone: no function of <paramref name="x"/> anywhere.
@@ -7788,9 +7801,10 @@ private static bool HasASquareRootOf(Entity expr, Entity.Variable x)
77887801
internal static Entity? SolveXOverACubicBinomialBesideTheRootOfAnother(Entity expr, Entity.Variable x, bool integrateByParts)
77897802
{
77907803
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
7791-
if (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var above) || above.Count != 1
7792-
|| !above.TryGetValue(EInteger.One, out var multiple) || multiple.ContainsNode(x))
7793-
return null;
7804+
// The root below the bar read before the numerator, which is the expensive half: under
7805+
// a substitution a sum of exponentials over a common denominator has no root below it
7806+
// and a numerator of thousands of terms, and expanding that to be told it is not x took
7807+
// seconds of the inverse hyperbolic substitution for `x^4 (d + e x^2)^3 (a + b arcosh(c x))`.
77947808
Entity? radicand = null;
77957809
Entity below = Number.Integer.One;
77967810
foreach (var factor in Mulf.LinearChildren(denominator))
@@ -7802,7 +7816,12 @@ private static bool HasASquareRootOf(Entity expr, Entity.Variable x)
78027816
}
78037817
else
78047818
below = below == Number.Integer.One ? factor : below * factor;
7805-
if (radicand is null || ACubicBinomial(radicand) is not var (c, d) || ACubicBinomial(below) is not var (a, b))
7819+
if (radicand is null)
7820+
return null;
7821+
if (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var above) || above.Count != 1
7822+
|| !above.TryGetValue(EInteger.One, out var multiple) || multiple.ContainsNode(x))
7823+
return null;
7824+
if (ACubicBinomial(radicand) is not var (c, d) || ACubicBinomial(below) is not var (a, b))
78067825
return null;
78077826
static bool Zero(Entity condition) => Functions.PartialFractions.IsZeroAsAValue(condition);
78087827
if (Zero(a) || Zero(b) || Zero(c) || Zero(d) || Zero(b * c - a * d))
@@ -23678,7 +23697,8 @@ Entity TakeOut(Entity node)
2367823697
if (!Integration.AnsweringTheQuestionAsked)
2367923698
return null;
2368023699
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
23681-
if (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var top) || top.Keys.Any(k => k.Sign < 0 || !k.CanFitInInt32()))
23700+
if (!HasAWholePowerPastTheFirstOf(denominator, x)
23701+
|| !TreeAnalyzer.TryGetPolynomial(numerator, x, out var top) || top.Keys.Any(k => k.Sign < 0 || !k.CanFitInInt32()))
2368223702
return null;
2368323703
Entity? quadratic = null;
2368423704
var power = 0;
@@ -24595,6 +24615,8 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
2459524615
if (!Integration.AnsweringTheQuestionAsked)
2459624616
return null;
2459724617
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
24618+
if (!HasAWholePowerPastTheFirstOf(denominator, x))
24619+
return null;
2459824620
Entity? quadratic = null;
2459924621
var power = 0;
2460024622
Entity? linear = null;
@@ -24718,6 +24740,8 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
2471824740
internal static Entity? SolveAPowerOfALinearBesideTheDerivativeOfAQuadraticOverItsPower(Entity expr, Entity.Variable x, bool integrateByParts)
2471924741
{
2472024742
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
24743+
if (!HasAWholePowerPastTheFirstOf(denominator, x))
24744+
return null;
2472124745
Entity? quadratic = null;
2472224746
var power = 0;
2472324747
Entity? linear = null;
@@ -32798,11 +32822,19 @@ private static (int, int) SignOfTheTwoRoots(Entity node, List<Entity> radicands)
3279832822
// sub-integrand of the chain.
3279932823
// A root of a quotient counts as the two roots it splits into -- a sum with a
3280032824
// quotient in it, `1/cos(x)^4 - 1`, being a quotient once combined.
32801-
if (expr.Nodes.Sum(node => node is Powf(var @base, Number.Rational half)
32802-
&& half is not Number.Integer && half.ERational.Denominator.Equals(EInteger.FromInt32(2))
32803-
&& @base.ContainsNode(x)
32804-
? (@base is Divf || Functions.SingleQuotient.Of(AsOneQuotient(@base)).Denominator != Number.Integer.One ? 2 : 1)
32805-
: 0) < 2)
32825+
// The root inside the logarithm an inverse hyperbolic function is written as counts as
32826+
// none: that logarithm is the inverse hyperbolic substitution's, which reads it as
32827+
// written, and combined, `ln(1/u + sqrt(1 + 1/u^2))` is `ln(1/u + sqrt(u^4 + u^2)/u^2)`,
32828+
// which nothing reads -- `u^7` beside it, from `x^3 arcsch(sqrt(x))`, was eighteen
32829+
// seconds of search to decline before the substitution answered it in a tenth of one.
32830+
// https://github.com/asc-community/AngouriMath/issues/233
32831+
if (RootsToCombine(expr) < 2)
32832+
return null;
32833+
if (expr.Nodes.Any(node => node is Logf(_, var inside) && HasARadicalOf(inside, x))
32834+
&& RootsToCombine(expr.Replace(node => node is Logf(var logarithmBase, var antilogarithm) && logarithmBase == MathS.e
32835+
&& antilogarithm.ContainsNode(x) && ReadAnInverseHyperbolic(antilogarithm, x) is not null
32836+
? Number.Integer.One
32837+
: node)) < 2)
3280632838
return null;
3280732839
var rewritten = CombineRadicalsIn(expr, x);
3280832840
// The rewriting is exact and the rewritten integrand is the question asked in
@@ -32832,6 +32864,13 @@ private static (int, int) SignOfTheTwoRoots(Entity node, List<Entity> radicands)
3283232864
&& !result.Nodes.Any(node => node == MathS.NaN))
3283332865
return sign * result;
3283432866
return null;
32867+
32868+
int RootsToCombine(Entity written)
32869+
=> written.Nodes.Sum(node => node is Powf(var @base, Number.Rational half)
32870+
&& half is not Number.Integer && half.ERational.Denominator.Equals(EInteger.FromInt32(2))
32871+
&& @base.ContainsNode(x)
32872+
? (@base is Divf || Functions.SingleQuotient.Of(AsOneQuotient(@base)).Denominator != Number.Integer.One ? 2 : 1)
32873+
: 0);
3283532874
}
3283632875

3283732876
/// <summary>
@@ -36255,6 +36294,11 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
3625536294
{
3625636295
if (!Integration.AnsweringTheQuestionAsked)
3625736296
return null;
36297+
// The arctangent the numerator needs is the integrand's: asked first, since the
36298+
// quotient below brings a sum of exponentials over one bar, every term's
36299+
// denominator multiplied into every other.
36300+
if (!expr.Nodes.Any(node => node is Arctanf && node.ContainsNode(x)))
36301+
return null;
3625836302
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
3625936303
Arctanf? arctangent = null;
3626036304
Entity? rate = null;
@@ -37174,29 +37218,23 @@ void Add(Entity root, Entity times)
3717437218

3717537219
/// <summary>
3717637220
/// Whether <paramref name="expr"/> over <paramref name="derivative"/> takes two values at
37177-
/// two points where <paramref name="candidate"/>, a sine, cosine, tangent, cotangent,
37178-
/// secant or cosecant of a linear, takes one -- every other symbol pinned -- which no
37179-
/// function of the candidate does. False for any other candidate, and wherever a value
37180-
/// cannot be had or the candidate itself does not repeat there.
37221+
/// two points where <paramref name="candidate"/> takes one -- every other symbol pinned --
37222+
/// which no function of the candidate does. False for a candidate
37223+
/// <see cref="TheOtherPointWhereTheCandidateRepeats"/> finds no such pair for, and wherever
37224+
/// a value cannot be had or the candidate itself does not repeat there.
3718137225
/// </summary>
3718237226
private static bool TheQuotientDisagreesWhereTheCandidateRepeats(Entity expr, Entity derivative, Entity candidate, Entity.Variable x)
3718337227
{
37184-
Entity? argument = candidate switch
37185-
{
37186-
Sinf(var a) => a, Cosf(var a) => a, Tanf(var a) => a,
37187-
Cotanf(var a) => a, Secantf(var a) => a, Cosecantf(var a) => a,
37188-
_ => null,
37189-
};
37190-
if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var intercept)
37191-
|| slope.Evaled is Number.Complex { IsZero: true })
37228+
// Past a trigonometric function itself, only where the quotient is cheap to evaluate: a
37229+
// special function's value is a series, and beside `x^5 erf(b x)^2` the even powers of x
37230+
// the search offers were four hundred evaluations that refused nothing, a quarter more
37231+
// of the integrand's allocation.
37232+
if (candidate is not (Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf)
37233+
&& (expr.Nodes.Any(IsASpecialFunction) || PowerOfXAsWritten(expr, x) > LargestPowerEvaluatedForARepeat))
3719237234
return false;
3719337235
Entity first = Number.Real.Create(EDecimal.FromString("0.37"));
37194-
Entity second = candidate switch
37195-
{
37196-
Sinf or Cosecantf => (MathS.pi - 2 * intercept) / slope - first,
37197-
Cosf or Secantf => -2 * intercept / slope - first,
37198-
_ => first + MathS.pi / slope,
37199-
};
37236+
if (TheOtherPointWhereTheCandidateRepeats(candidate, x, first) is not { } second)
37237+
return false;
3720037238
var pinned = new Dictionary<Variable, Entity>();
3720137239
var values = new[] { "1.37", "0.61", "2.23", "1.91", "0.83", "1.13" };
3720237240
var index = 0;
@@ -37210,17 +37248,137 @@ private static bool TheQuotientDisagreesWhereTheCandidateRepeats(Entity expr, En
3721037248
value = value.Substitute(pair.Key, pair.Value);
3721137249
return value.Evaled is Number.Complex { IsNaN: false } number && number.IsFinite ? number : null;
3721237250
}
37213-
static bool Close(Number.Complex left, Number.Complex right, double tolerance)
37251+
// Compared in doubles, and not compared where a double does not hold the value. At a
37252+
// rational point the values are exact rationals, and for `x^24 (a x + b x^38)^12` under
37253+
// `u = x^24` each has two thousand digits: their difference, taken as numbers, ran for
37254+
// minutes, where the doubles say in a millisecond that they are opposite.
37255+
static bool? Close(Number.Complex left, Number.Complex right, double tolerance)
3721437256
{
37215-
var scale = System.Math.Max(1.0, ((Number.Real)left.Abs()).EDecimal.ToDouble());
37216-
return ((Number.Real)(left - right).Abs()).EDecimal.ToDouble() <= tolerance * scale;
37257+
var (leftReal, leftImaginary) = (left.RealPart.EDecimal.ToDouble(), left.ImaginaryPart.EDecimal.ToDouble());
37258+
var (rightReal, rightImaginary) = (right.RealPart.EDecimal.ToDouble(), right.ImaginaryPart.EDecimal.ToDouble());
37259+
var scale = System.Math.Max(1.0, System.Math.Sqrt(leftReal * leftReal + leftImaginary * leftImaginary));
37260+
var difference = System.Math.Sqrt((leftReal - rightReal) * (leftReal - rightReal) + (leftImaginary - rightImaginary) * (leftImaginary - rightImaginary));
37261+
return !double.IsNaN(scale) && !double.IsInfinity(scale) && !double.IsNaN(difference) && !double.IsInfinity(difference)
37262+
? difference <= tolerance * scale : null;
3721737263
}
3721837264
if (At(candidate, first) is not { } candidateFirst || At(candidate, second) is not { } candidateSecond
37219-
|| !Close(candidateFirst, candidateSecond, 1e-9))
37265+
|| Close(candidateFirst, candidateSecond, 1e-9) is not true)
3722037266
return false;
3722137267
var quotient = expr / derivative;
3722237268
return At(quotient, first) is { } quotientFirst && At(quotient, second) is { } quotientSecond
37223-
&& !Close(quotientFirst, quotientSecond, 1e-6);
37269+
&& Close(quotientFirst, quotientSecond, 1e-6) is false;
37270+
}
37271+
37272+
/// <summary>
37273+
/// The power of x past which <see cref="TheQuotientDisagreesWhereTheCandidateRepeats"/> does
37274+
/// not evaluate for a candidate other than a trigonometric function. The values at the two
37275+
/// points are exact, and their digits grow with the power: `x^36 (a + b x^37)^12` offers
37276+
/// `u = x^36`, and evaluating its quotient at the two points, for every expansion the search
37277+
/// asks, took the row from 68 ms to 296.
37278+
/// </summary>
37279+
private const long LargestPowerEvaluatedForARepeat = 64;
37280+
37281+
/// <summary>
37282+
/// The largest power <paramref name="x"/> is raised to in <paramref name="expr"/> as written:
37283+
/// the powers of a product added, of a sum the largest, a whole power multiplying its base's,
37284+
/// on either side of the bar; capped, so that it cannot overflow.
37285+
/// </summary>
37286+
private static long PowerOfXAsWritten(Entity expr, Entity.Variable x)
37287+
{
37288+
const long cap = 1L << 40;
37289+
long Of(Entity node) => System.Math.Min(cap, node switch
37290+
{
37291+
_ when !node.ContainsNode(x) => 0,
37292+
Variable => 1,
37293+
Powf(var @base, Number.Integer { EInteger: var power }) => Of(@base) * (power.Abs().CompareTo(EInteger.FromInt64(cap)) > 0 ? cap : power.Abs().ToInt64Checked()),
37294+
Mulf(var left, var right) => Of(left) + Of(right),
37295+
Divf(var left, var right) => Of(left) + Of(right),
37296+
_ => node.DirectChildren.Max(Of),
37297+
});
37298+
return Of(expr);
37299+
}
37300+
37301+
/// <summary>
37302+
/// A point other than <paramref name="first"/> where <paramref name="candidate"/> takes the
37303+
/// value it takes at <paramref name="first"/>, or null. A function of one sine, cosine,
37304+
/// tangent, cotangent, secant or cosecant of a linear, with <paramref name="x"/> nowhere
37305+
/// else, repeats where that function does: a sine at <c>pi - theta</c>, a cosine at
37306+
/// <c>-theta</c>, a tangent a period on. A quadratic repeats across its vertex, and a
37307+
/// constant times an even power of a quotient of linears, or of one linear, where the
37308+
/// quotient takes the opposite value.
37309+
/// </summary>
37310+
/// <remarks>
37311+
/// The trigonometric function alone was read, and a multiple of one was not:
37312+
/// <c>sin(x) tan(6x)</c> written in the sine and cosine of <c>x</c> offers <c>2 cos(x)</c>, and
37313+
/// simplifying the integrand over its derivative to refuse it was eight seconds. The
37314+
/// quotient by the derivative of <c>e ((c + d x)/(a + b x))^2</c> holds the quotient itself,
37315+
/// which the square does not determine -- nine seconds of Rubi's 3.2.1
37316+
/// <c>1/((a g + b g x)^2 (A + B ln(e (c + d x)^2/(a + b x)^2))^2)</c> -- and that over the
37317+
/// derivative of <c>a + b x + c x^2</c> holds the linears of
37318+
/// <c>(a + b x + c x^2)^(5/2)/((d + e x)(f + g x))</c>, five more.
37319+
/// https://github.com/asc-community/AngouriMath/issues/233
37320+
/// </remarks>
37321+
private static Entity? TheOtherPointWhereTheCandidateRepeats(Entity candidate, Entity.Variable x, Entity first)
37322+
{
37323+
var trigonometric = candidate.Nodes
37324+
.Where(node => node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf && node.ContainsNode(x)).Distinct().ToList();
37325+
if (trigonometric.Count == 1)
37326+
{
37327+
var function = trigonometric[0];
37328+
if (candidate.Replace(node => node == function ? Number.Integer.One : node).ContainsNode(x)
37329+
|| !TreeAnalyzer.TryGetPolyLinear(function.DirectChildren.First(), x, out var slope, out var intercept)
37330+
|| slope.Evaled is Number.Complex { IsZero: true })
37331+
return null;
37332+
return function switch
37333+
{
37334+
Sinf or Cosecantf => (MathS.pi - 2 * intercept) / slope - first,
37335+
Cosf or Secantf => -2 * intercept / slope - first,
37336+
_ => first + MathS.pi / slope,
37337+
};
37338+
}
37339+
if (trigonometric.Count > 1 || !IsARationalFunction(candidate, x))
37340+
return null;
37341+
if (candidate is Sumf or Minusf && TreeAnalyzer.TryGetPolyQuadratic(candidate, x, out var leading, out var linear, out _)
37342+
&& leading.Evaled is not Number.Complex { IsZero: true })
37343+
return -linear / leading - first;
37344+
// k ((p + q x)/(r + s x))^n for an even n, or k (p + q x)^n: the quotient R takes -R(first) at
37345+
// -(p + R(first) r)/(q + R(first) s).
37346+
Entity? above = null, below = null;
37347+
EInteger? power = null;
37348+
foreach (var (factor, underneath) in FactorsOfTheIntegrand(candidate))
37349+
{
37350+
if (!factor.ContainsNode(x))
37351+
continue;
37352+
if (factor is not Powf(var @base, Number.Integer { EInteger: var raised }) || raised.IsZero)
37353+
return null;
37354+
var signed = underneath ? raised.Negate() : raised;
37355+
if (!signed.Abs().Remainder(EInteger.FromInt32(2)).IsZero || power is { } known && !known.Equals(signed.Negate()))
37356+
return null;
37357+
if (signed.Sign > 0 && above is null)
37358+
above = @base;
37359+
else if (signed.Sign < 0 && below is null)
37360+
below = @base;
37361+
else
37362+
return null;
37363+
power = signed;
37364+
}
37365+
if (power is null)
37366+
return null;
37367+
Entity p = Number.Integer.One, q = Number.Integer.Zero, r = Number.Integer.One, s = Number.Integer.Zero;
37368+
if (above is not null)
37369+
{
37370+
if (!TreeAnalyzer.TryGetPolyLinear(above, x, out var aboveSlope, out var aboveIntercept) || aboveSlope is null || aboveIntercept is null)
37371+
return null;
37372+
(q, p) = (aboveSlope, aboveIntercept);
37373+
}
37374+
if (below is not null)
37375+
{
37376+
if (!TreeAnalyzer.TryGetPolyLinear(below, x, out var belowSlope, out var belowIntercept) || belowSlope is null || belowIntercept is null)
37377+
return null;
37378+
(s, r) = (belowSlope, belowIntercept);
37379+
}
37380+
var atFirst = (above ?? Number.Integer.One).Substitute(x, first) / (below ?? Number.Integer.One).Substitute(x, first);
37381+
return -(p + atFirst * r) / (q + atFirst * s);
3722437382
}
3722537383

3722637384
/// <summary>

0 commit comments

Comments
 (0)