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
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,11 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
/// <c>e^L</c>, which the exponential substitution answers in real terms; with it true, only those,
/// after that substitution has declined them. Asked only of the question asked, and last: the
/// question in <c>s</c> is not cheap to decline, and asked early it took time from the rules
/// that answer <c>x/cosh(x)^(7/2) + 3/5 x sqrt(cosh(x))</c> and the like.
/// that answer <c>x/cosh(x)^(7/2) + 3/5 x sqrt(cosh(x))</c> and the like. Nor in the first pass
/// where the exponential substitution wants a sum over roots and the question in <c>s</c>, a
/// rational function of <c>cos(s)</c> or <c>sin(s)</c> alone, wants one too, by a factor below
/// the bar of degree above two: the second pass answers it in <c>e^L</c>, and the question in
/// <c>s</c> took seconds to decline, five gigabytes for <c>1/(1 + sinh(x)^3)</c> against 0.2.
/// </remarks>
internal static Entity? SolveAHyperbolicIntegrandAsATrigonometricOne(Entity expr, Entity.Variable x, bool integrateByParts, bool rationalToo)
{
Expand Down Expand Up @@ -1190,6 +1194,10 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
});
if (!sawASineOrCosine || inS.ContainsNode(w))
return null;
// Wanting a sum over roots in e^L and, by the denominator, in s too: the second pass's.
if (rationalToo && Integration.ASumOverRootsWouldAnswer && !Integration.SumsOverRootsAllowed
&& AFactorAboveAQuadraticInS(inS, s))
return null;
var question = MathS.i / slopeOfL * inS;
if (Integration.ComputeAsAQuestionOfItsOwn(question, s, integrateByParts) is not { } inTermsOfS
|| inTermsOfS.Nodes.Any(node => node is Integralf || node == MathS.NaN))
Expand All @@ -1209,6 +1217,53 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
}).Substitute(s, -MathS.i * linear).InnerSimplified;
return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;

// Whether inS, rational in cos(s) alone or sin(s) alone, has a denominator of degree three or
// more that t = tan(s/2) or t = tan(s) leaves with an irreducible factor of degree above two,
// which the rational rules write only as a sum over roots. The cosine, and the square of the
// sine, are quotients of linears in t^2 there, so a factor over the rationals keeps its
// degree: 1 + cos(s)^8 keeps 1 + v^4, and 1 - sin(s)^8 splits. A sine to odd powers is
// 2t/(1 + t^2), not a function of t^2, and the cubics and quintics in it all declined.
static bool AFactorAboveAQuadraticInS(Entity inS, Variable s)
{
foreach (var circular in new Entity[] { MathS.Cos(s), MathS.Sin(s) })
{
var y = Variable.CreateUnique(inS, "y_osb");
var inY = inS.Replace(node => node == circular ? y : node);
if (inY.ContainsNode(s) || !inY.ContainsNode(y) || !IsARationalFunction(inY, y))
continue;
if (!TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(inY).Denominator, y, out var read))
return false;
var terms = read.Where(term => term.Value.Evaled is not Number.Complex { IsZero: true }).ToList();
if (terms.Any(term => term.Key.Sign < 0) || !terms.Any(term => term.Key.CompareTo(EInteger.FromInt32(3)) >= 0))
return false;
if (terms.Any(term => !term.Key.IsEven))
return circular is Sinf || AFactorAboveAQuadratic(terms, halved: false);
return AFactorAboveAQuadratic(terms, halved: true);
}
return false;

// Over the rationals; false where a coefficient is not rational, as nothing is known then.
static bool AFactorAboveAQuadratic(List<KeyValuePair<EInteger, Entity>> terms, bool halved)
{
var rationals = new Dictionary<int, ERational>();
var denominator = EInteger.One;
foreach (var term in terms)
{
var (power, coefficient) = (term.Key, term.Value);
if (coefficient.Evaled is not Number.Rational rational || !power.CanFitInInt32() || power.ToInt32Checked() > 2 * IntegerPolynomial.MaxDegree)
return false;
rationals[halved ? power.ToInt32Checked() / 2 : power.ToInt32Checked()] = rational.ERational;
denominator = denominator * rational.ERational.Denominator / denominator.Gcd(rational.ERational.Denominator);
}
var whole = new EInteger[rationals.Keys.Max() + 1];
for (var k = 0; k < whole.Length; k++)
whole[k] = rationals.TryGetValue(k, out var c) ? c.Numerator * (denominator / c.Denominator) : EInteger.Zero;
return IntegerPolynomial.Create(whole) is { Degree: > 2 and <= IntegerPolynomial.MaxDegree } polynomial
&& Functions.PolynomialFactorization.FactorPrimitive(polynomial.PrimitivePart()) is { } parts
&& parts.Any(part => part.Factor.Degree > 2);
}
}

// +1 for W^2 + 1 and -1 for W^2 - 1, as polynomials in W; null for anything else.
static int? Squared(Entity polynomial, Variable w)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public sealed class AHyperbolicIntegrandAsATrigonometricOneIntegralTest
[InlineData("sinh(x)/(a + b*sinh(x) + c*sinh(x)^2)")]
// Rubi's 6.5.3.
[InlineData("coth(a + b*x)/sqrt(c + b*sech(a + b*x))")]
public void AsTheTrigonometricQuestion(string integrand)
public void AsTheTrigonometricQuestion(string integrand) => AnsweredAndCheckedBack(integrand);

private static string AnsweredAndCheckedBack(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Expand All @@ -53,8 +55,26 @@ public void AsTheTrigonometricQuestion(string integrand)
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
return text;
}

// Wanting a sum over roots in e^x, these are asked in s only where s does not want one too:
// there the trigonometric answer stands, in closed form.
[Theory]
[InlineData("1/(1 + sinh(x)^4)")]
[InlineData("1/(1 + cosh(x)^3)")]
[InlineData("sech(x)^2/(3 - 4*tanh(x)^3)")]
public void KeepsTheClosedFormWhereSFactorsIntoQuadratics(string integrand)
=> Assert.DoesNotContain("sum(", AnsweredAndCheckedBack(integrand));

// And where s wants one too, as for a cubic in sin(s) or 1 + cos(s)^8, the second pass answers
// in e^x, as before the rule.
[Theory]
[InlineData("1/(1 + sinh(x)^3)")]
[InlineData("1/(1 + cosh(x)^8)")]
public void LeavesTheSumOverRootsToTheSecondPass(string integrand)
=> Assert.Contains("sum(", AnsweredAndCheckedBack(integrand));

// Asked before the rules in x, the question in s took the time they answer these in.
[Theory]
[InlineData("x/cosh(x)^(7/2) + 3/5*x*sqrt(cosh(x))")]
Expand Down
Loading