@@ -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