Skip to content

Commit a8f503b

Browse files
Split a rational function over a binomial of degree three or four beside other factors (#2039)
TrySplitOverWrittenFactors read linear and quadratic factors only, and the tangent's 1/((a + b u^3) (1 + u^2)), from Rubi's 4.3.7 1/(a + b tan(c + d x)^3), and the root of the sine's 2 u/((a + b u) (1 + u^4)), from 6.1.7's sech(c + d x)/(a + b sqrt(sinh(c + d x))), were declined for want of a split. The system reads any degree, and the decomposition is checked at sampled points before it is returned; it now admits one binomial of degree three or four written once, and the rules for a polynomial over a binomial answer the pieces. Not beside a power of the variable alone, nor beside a second such binomial: the cube root's substitution makes Rubi's 1.1.3.4 x^m (a + b x^3)^(1/3)/(c + d x^3) rational functions over u^3 (c + (a d - b c) u^3) and over (b u^3 - 1) (c + (a d - b c) u^3) with a^9 c^36 d^9 and a^17 b^16 c^43 in their numerators, and split, nine of them came back two to nine times longer -- (a + b x^3)^(1/3)/(x^5 (c + d x^3)) in 12,377 characters where the rules for the cube root write 1,964 -- and three were wrong in the corpus's judgement. Fenced, those nine are master's answers to the character. 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 8d5f693 commit a8f503b

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,23 @@ carried the discriminant's being real in its condition, and held nowhere when it
770770
| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().DomainCondition` | `false` | `not x = 0` |
771771
| `"1/(sqrt(a + i*a*x)*(c + d*x)^(3/2)*(a - i*a*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer with no value at any point | 3,267 characters, defined |
772772

773+
### A rational function over a binomial of degree three or four beside other factors is split over them
774+
775+
**Answers where there were none.** Rubi's 4.3.7 `1/(a + b tan(c + d x)^3)` and its square, and 6.1.7
776+
`sech(c + d x)/(a + b sqrt(sinh(c + d x)))` and its square, were declined. Under the tangent and under the root of the
777+
sine they are rational functions over `(a + b u^3) (1 + u^2)` and `(a + b u) (1 + u^4)`, and the split over the
778+
factors as written read linear and quadratic ones only. It now reads one binomial of degree three or four, written
779+
once, beside factors of degree one or two, and the rules for a polynomial over a binomial answer each piece; not
780+
beside a power of the variable alone, which those rules read term by term, nor beside a second such binomial
781+
([#233](https://github.com/asc-community/AngouriMath/issues/233)).
782+
783+
| Input | Was (2.5.0) | Now |
784+
|---|---|---|
785+
| `"1/(a+b*tan(c+d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 1,853 characters |
786+
| `"1/(a+b*tan(c+d*x)^3)^2".ToEntity().Integrate("x")` | `integral(...)` | 3,026 characters |
787+
| `"sech(c+d*x)/(a+b*sqrt(sinh(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 3,325 characters |
788+
| `"sech(c+d*x)/(a+b*sqrt(sinh(c+d*x)))^2".ToEntity().Integrate("x")` | `integral(...)` | 4,614 characters |
789+
773790
### A rational function over the quadratic of an inverse tangent beside it is split
774791

775792
**Answers where there were none.** `x^4 atan(a x)/(c + a^2 c x^2)` ran out of time, and so did three more of Rubi's 5.3.4.

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ internal static bool TrySplitOverWrittenFactors(
438438
// coefficients free of x; a factor free of x is a constant and comes out in front.
439439
var factors = new List<(Entity Factor, int Degree)>();
440440
Entity constant = Integer.One;
441+
var binomialsPastTheSecondDegree = 0;
442+
var aPowerOfXAlone = false;
441443
foreach (var part in Mulf.LinearChildren(denominator))
442444
{
443445
if (!part.ContainsNode(x))
@@ -464,8 +466,16 @@ internal static bool TrySplitOverWrittenFactors(
464466
if (!TreeAnalyzer.TryGetPolynomial(toRead, x, out var read) || read.Count == 0)
465467
return false;
466468
var degree = read.Keys.Max()!;
467-
if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() is not (1 or 2))
469+
// And a binomial of degree three or four written once, `a + b x^3`: the system below
470+
// reads any degree, and the rules for a polynomial over a binomial answer each
471+
// piece -- `1/((a + b u^3) (1 + u^2))`, which the tangent makes of Rubi's
472+
// `1/(a + b tan(c + d x)^3)`, was declined for want of a split.
473+
var aBinomialOnce = read.Count == 2 && multiplicity == 1 && degree.CanFitInInt32() && degree.ToInt32Unchecked() is 3 or 4;
474+
if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() is not (1 or 2) && !aBinomialOnce)
468475
return false;
476+
if (aBinomialOnce)
477+
binomialsPastTheSecondDegree++;
478+
aPowerOfXAlone |= read.Count == 1;
469479
// A repeated quadratic has a rule to land on only with a symbol somewhere,
470480
// the split by residues below; over the rationals the system would hand it a
471481
// cubic over the square, which no rule reads.
@@ -476,6 +486,15 @@ internal static bool TrySplitOverWrittenFactors(
476486
return false;
477487
factors.Add((part.InnerSimplified, degree.ToInt32Unchecked() * multiplicity));
478488
}
489+
// Not beside a power of x alone, which is the rules' for `x^m` over a binomial, term by
490+
// term: the cube root's substitution makes Rubi's 1.1.3.4 `(a + b x^3)^(1/3)/(x^5 (c + d x^3))`
491+
// a polynomial over `u^3 (c + (a d - b c) u^3)` with `a^9 c^36 d^9` in it, and split over
492+
// the two, it came back in 12,377 characters where those rules write 1,964. Nor beside a
493+
// second such binomial: `x^7 (a + b x^3)^(1/3)/(c + d x^3)` is one over `(b u^3 - 1)` and
494+
// `(c + (a d - b c) u^3)`, whose system with symbols in it came back with `a^17 b^16 c^43`
495+
// and a wrong answer, where the rules for the cube root answer it in 2,861 characters.
496+
if (binomialsPastTheSecondDegree > 1 || binomialsPastTheSecondDegree == 1 && aPowerOfXAlone)
497+
return false;
479498
// Two linear factors with one root are one factor: `(a + b x)(a + x b)^2` is
480499
// written so by the rules that make a factor monic and gather its powers, and
481500
// as two distinct factors the decomposition has no answer -- its coefficients
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
/// A rational function with symbols in it over a product written with a binomial of degree three
16+
/// or four in it, split over the factors as written by undetermined coefficients: each piece is a
17+
/// polynomial over a binomial, or over a linear or a quadratic, which the rules for those answer.
18+
/// The split read only linear and quadratic factors, and `1/((a + b u^3) (1 + u^2))`, which the
19+
/// tangent makes of Rubi's 4.3.7 `1/(a + b tan(c + d x)^3)`, was declined.
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
21+
/// </summary>
22+
[Trait("Area", "Calculus")]
23+
public sealed class ARationalFunctionOverAWrittenBinomialOfDegreeThreeOrFourIntegralTest
24+
{
25+
[Theory]
26+
[InlineData("1/((a+b*x^3)*(1+x^2))")]
27+
[InlineData("1/((a+b*x^4)*(c+x^2))")]
28+
[InlineData("1/(a+b*tan(c+d*x)^3)")]
29+
[InlineData("1/(a+b*tan(c+d*x)^3)^2")]
30+
public void DifferentiatesBack(string integrand)
31+
{
32+
var integral = integrand.ToEntity().Integrate("x");
33+
Assert.DoesNotContain("integral(", integral.Stringize());
34+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
35+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
36+
var original = Pinned(integrand.ToEntity());
37+
var compared = 0;
38+
foreach (var at in new[] { -1.3, -0.6, -0.2, 0.3, 0.8, 1.4 })
39+
{
40+
var want = original.Substitute("x", at).EvalNumerical();
41+
var got = derivative.Substitute("x", at).EvalNumerical();
42+
if (want.IsNaN || got.IsNaN)
43+
continue;
44+
compared++;
45+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
46+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
47+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
48+
}
49+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
50+
}
51+
52+
/// <summary>
53+
/// Not split beside a power of the variable alone, nor beside a second such binomial: the cube
54+
/// root's substitution makes Rubi's 1.1.3.4 rows rational functions over <c>u^3 (c + k u^3)</c>
55+
/// and over <c>(b u^3 - 1)(c + k u^3)</c>, and split, they came back in 12,377 and 27,590
56+
/// characters with <c>c^36</c> and <c>c^43</c> in them, the second wrong, where the rules for
57+
/// the cube root write 1,964 and 2,861.
58+
/// </summary>
59+
[Theory]
60+
[InlineData("(a+b*x^3)^(1/3)/(x^5*(c+d*x^3))")]
61+
[InlineData("x^7*(a+b*x^3)^(1/3)/(c+d*x^3)")]
62+
public void IsNotSplitBesideAPowerOfXOrASecondBinomial(string integrand)
63+
{
64+
var integral = integrand.ToEntity().Integrate("x");
65+
var text = integral.Stringize();
66+
Assert.DoesNotContain("integral(", text);
67+
Assert.True(text.Length < 5000, $"the antiderivative of {integrand} is {text.Length} characters");
68+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
69+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
70+
var original = Pinned(integrand.ToEntity());
71+
var compared = 0;
72+
foreach (var at in new[] { 0.3, 0.6, 0.9, 1.2, 1.5, 1.8 })
73+
{
74+
var want = original.Substitute("x", at).EvalNumerical();
75+
var got = derivative.Substitute("x", at).EvalNumerical();
76+
if (want.IsNaN || got.IsNaN)
77+
continue;
78+
compared++;
79+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
80+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
81+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
82+
}
83+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)