Skip to content

Commit 68ae16c

Browse files
Write out the factors the half tangent shares between the parts it homogenised apart (#2055)
Rubi's 6.7.1 sech(x)^2/(a + c sech(x) + b tanh(x)) ran past the corpus's budget; master answered it in 1,383,216 characters after twenty-two seconds. SolveARationalFunctionOfTheHyperbolicTangent homogenises each maximal rational function of e^x apart, which keeps the written factors, and leaves a factor two parts share inside the expanded polynomial of one: under u = tanh(x/2) the integrand was (1 + u^2)^2 over (1 + u^2)^2 times a quartic holding 1 + u^2 once more, and the rational integrator wrote that quartic's partial fractions with symbols. The factors the homogenising makes, 1 + u^2, 1 - u^2, 1 - u, 1 + u and u, are now divided out of every written factor of the denominator as often as they go exactly and written as powers of their own, then out of the numerator: the integrand is 1/((1 + u^2) Q(u)) for a quadratic Q, 2,931 characters in 0.6 s. Only where one of them then divides the numerator too: written out with nothing cancelled, 6.3.1's (c + d x)^3/(a + a tanh(e + f x))^3 ran past the budget it was answered in. And 1 - u^2 before its two linears, so that an integrand even in u stays written so: split into them, 6.1.7's sinh(u)^8/(a - b sinh(u)^4)^2 took 5.4 GB where it takes 0.5. A polynomial gcd over the whole quotient was tried first; it cancels the same factor, but expands the denominator, and the rational integrator declined the quartic it left. 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 07f3e15 commit 68ae16c

4 files changed

Lines changed: 155 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,21 @@ was declined. Each of its terms alone is not elementary, and the sum is the deri
694694
|---|---|---|
695695
| `"e^(a+b*x)*sinh(c+d*x)^(3/2)-3/4*e^(a+b*x)*d^2/((b^2-9/4*d^2)*sqrt(sinh(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 249 characters |
696696

697+
### Under the half tangent, a factor the parts share is written out
698+
699+
**Answers where there were none, shorter than master's.** Rubi's 6.7.1 `sech(x)^2/(a + c sech(x) + b tanh(x))` ran past
700+
the corpus's budget. Master answered it in 1,383,216 characters after twenty-two seconds. Under `u = tanh(x/2)` the parts
701+
of the integrand are homogenised apart, and a factor they share stayed hidden inside the expanded quartic of one of them:
702+
`(1 + u^2)^2` over `(1 + u^2)^2` times a quartic that holds `1 + u^2` once more. The factors `1 + u^2`, `1 - u^2`, `1 - u`, `1 + u`
703+
and `u`, which the homogenising makes, are now divided out of the written factors of the denominator and out of the
704+
numerator, and the integrand is `1/((1 + u^2) Q(u))` for a quadratic Q
705+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
706+
707+
| Input | Was (2.5.0) | Now |
708+
|---|---|---|
709+
| `"sech(x)^2/(a+c*sech(x)+b*tanh(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,931 characters, where before this change master gave 1,383,216 |
710+
| `"csch(x)^2/(a+c*csch(x)+b*coth(x))".ToEntity().Integrate("x")` | `integral(...)` | 1,766 characters, where before this change master gave 1,690 |
711+
697712
### A function of the hyperbolic tangent alone is integrated under `u = tanh`
698713

699714
**Answers where there were none.** `tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4)` was declined, and so were 35 more of

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,87 @@ private static Entity WithThePrimitiveDenominator(Entity quotient)
882882
return primitive.IsConstant ? scaledTop : scaledTop / primitive.ToEntity(variables);
883883
}
884884

885+
/// <summary>
886+
/// <paramref name="numerator"/> over <paramref name="denominator"/>, a product of powers as
887+
/// written, with each of <paramref name="candidates"/> divided out of every written factor of
888+
/// the denominator as often as it goes exactly and written as a power of its own, and then
889+
/// out of the numerator as often as it goes there too. Null where no candidate divides a
890+
/// factor it is not already, where none then divides the numerator, or where a side does not
891+
/// read as a polynomial over the rationals.
892+
/// </summary>
893+
/// <remarks>
894+
/// A substitution homogenises the parts of an integrand apart, and a factor they share is
895+
/// then hidden inside the expanded quartic of one of them: under <c>u = tanh(x/2)</c>,
896+
/// <c>sech(x)^2/(a + c sech(x) + b tanh(x))</c> is <c>(1 + u^2)^2</c> over <c>(1 + u^2)^2</c>
897+
/// times a quartic that holds <c>1 + u^2</c> once more. Written out, it is <c>1/((1 + u^2) Q(u))</c>
898+
/// with Q a quadratic, which the rational rules read.
899+
/// https://github.com/asc-community/AngouriMath/issues/718
900+
/// </remarks>
901+
internal static Entity? WithTheCandidateFactorsWrittenOut(Entity numerator, Entity denominator, IReadOnlyList<Entity> candidates)
902+
{
903+
var variables = numerator.Vars.Concat(denominator.Vars).Concat(candidates.SelectMany(c => c.Vars)).Distinct()
904+
.OrderBy(v => v.Name, System.StringComparer.Ordinal).ToArray();
905+
if (variables.Length == 0 || variables.Length > MultivariatePolynomial.MaxVariables)
906+
return null;
907+
var indices = new Dictionary<Variable, int>(variables.Length);
908+
for (var i = 0; i < variables.Length; i++)
909+
indices[variables[i]] = i;
910+
var parsed = new List<MultivariatePolynomial>();
911+
foreach (var candidate in candidates)
912+
if (MultivariatePolynomial.TryParse(candidate, indices) is { IsConstant: false } read)
913+
parsed.Add(read);
914+
else
915+
return null;
916+
var times = new int[parsed.Count];
917+
var changed = false;
918+
Entity rest = Integer.One;
919+
foreach (var written in Mulf.LinearChildren(denominator))
920+
{
921+
var (@base, power) = written is Powf(var repeated, Integer { EInteger.Sign: > 0 } raised) && raised.EInteger.CanFitInInt32()
922+
? (repeated, raised.EInteger.ToInt32Unchecked()) : (written, 1);
923+
if (!@base.Vars.Any() || MultivariatePolynomial.TryParse(@base, indices) is not { IsConstant: false } factor)
924+
{
925+
rest *= written;
926+
continue;
927+
}
928+
var divided = false;
929+
for (var k = 0; k < parsed.Count; k++)
930+
while (factor.DivideExact(parsed[k]) is { } quotient)
931+
{
932+
// A factor that is the candidate itself, to a power, is already written out.
933+
divided |= !quotient.IsConstant;
934+
factor = quotient;
935+
times[k] += power;
936+
}
937+
changed |= divided;
938+
var remaining = factor.ToEntity(variables);
939+
if (remaining != Integer.One)
940+
rest *= power == 1 ? remaining : MathS.Pow(remaining, power);
941+
}
942+
if (!changed)
943+
return null;
944+
if (MultivariatePolynomial.TryParse(numerator, indices) is not { } top)
945+
return null;
946+
// Only where the numerator shares one of them: written out with nothing cancelled, the
947+
// tangent's `(c + d x)^3/(a + a tanh(e + f x))^3` ran past the budget it was answered in.
948+
var cancelled = false;
949+
for (var k = 0; k < parsed.Count; k++)
950+
while (times[k] > 0 && top.DivideExact(parsed[k]) is { } quotient)
951+
{
952+
top = quotient;
953+
times[k]--;
954+
cancelled = true;
955+
}
956+
if (!cancelled)
957+
return null;
958+
Entity below = rest;
959+
for (var k = 0; k < parsed.Count; k++)
960+
if (times[k] > 0)
961+
below *= times[k] == 1 ? candidates[k] : MathS.Pow(candidates[k], times[k]);
962+
var above = top.ToEntity(variables);
963+
return below == Integer.One ? above : above / below;
964+
}
965+
885966
/// <summary>
886967
/// <paramref name="numerator"/> over <paramref name="denominator"/>, a product of
887968
/// powers as written, with every written factor that divides the numerator exactly

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34530,6 +34530,15 @@ private static bool IsTheZeroPolynomial(Entity expr)
3453034530
&& Mulf.LinearChildren(below).All(factor => !factor.ContainsNode(t) || factor == t || factor is Powf(var b, Number.Integer) && b == t)))
3453134531
continue;
3453234532
var rationalInT = WithThePowersOfOnePlusMinusTCollected(inT / (1 - MathS.Sqr(t)));
34533+
// With the factors the parts were homogenised over written out of the rest: `sech(x)^2`
34534+
// and `a + c sech(x) + b tanh(x)` are each over `1 + t^2`, and the one in the quartic
34535+
// below was hidden, where the rational integrator wrote 1.38 million characters.
34536+
// `1 - t^2` before its two linears, so that an integrand even in t is written so: split
34537+
// into them, `sinh(u)^8/(a - b sinh(u)^4)^2` took ten times the memory.
34538+
if (Functions.SingleQuotient.Of(rationalInT) is var (aboveT, belowT) && belowT.ContainsNode(t)
34539+
&& Functions.PartialFractions.WithTheCandidateFactorsWrittenOut(aboveT, belowT,
34540+
new Entity[] { 1 + MathS.Sqr(t), 1 - MathS.Sqr(t), 1 - t, 1 + t, t }) is { } writtenOut)
34541+
rationalInT = writtenOut;
3453334542
if (rationalInT.ContainsNode(square) || rationalInT.ContainsNode(vt) || rationalInT.Nodes.Any(node => node == MathS.NaN)
3453434543
|| Integration.ComputeAsAQuestionOfItsOwn(rationalInT, t, integrateByParts) is not { } evenResult)
3453534544
return null;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// Under <c>u = tanh(x/2)</c> the parts of an integrand are homogenised apart, and a factor they
16+
/// share was left hidden inside the expanded quartic of one of them: Rubi's 6.7.1
17+
/// <c>sech(x)^2/(a + c sech(x) + b tanh(x))</c> is <c>(1 + u^2)^2</c> over <c>(1 + u^2)^2</c> times a
18+
/// quartic holding <c>1 + u^2</c> once more, and the rational integrator wrote 1.38 million
19+
/// characters in twenty-two seconds, past the corpus's budget. Written out, it is
20+
/// <c>1/((1 + u^2) Q(u))</c> for a quadratic Q.
21+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
22+
/// </summary>
23+
[Trait("Area", "Calculus")]
24+
public sealed class TheHalfTangentsFactorsWrittenOutIntegralTest
25+
{
26+
[Theory]
27+
// Rubi 6.7.1:910.
28+
[InlineData("sech(x)^2/(a + c*sech(x) + b*tanh(x))")]
29+
[InlineData("csch(x)^2/(a + c*csch(x) + b*coth(x))")]
30+
public void IsAnsweredShortly(string integrand)
31+
{
32+
var integral = integrand.ToEntity().Integrate("x");
33+
var text = integral.Stringize();
34+
Assert.DoesNotContain("integral(", text);
35+
Assert.True(text.Length < 5000, $"{integrand} came back in {text.Length} characters");
36+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4);
37+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
38+
var original = Pinned(integrand.ToEntity());
39+
foreach (var at in new[] { -2.2, -0.9, 0.3, 1.4, 2.1 })
40+
{
41+
var want = original.Substitute("x", at).EvalNumerical();
42+
var got = derivative.Substitute("x", at).EvalNumerical();
43+
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
44+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
45+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
46+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)