Skip to content

Commit 47ba93b

Browse files
Integrate a quotient whose numerator is not constant (#233) (#681)
The table matched k/(ax^2 + bx + c) only when k did not mention x, so anything with a variable on top had no antiderivative at all: int x / (x^2 + 2x + 5) int (x + 3) / (x^2 + 3x + 2) int x / (x + 1) A linear numerator is a multiple of the denominator's derivative plus a constant: px + q = (p/2a)(2ax + b) + (q - pb/2a). The first part integrates to a logarithm and the second is the constant-numerator case that was already there, so this only needs the rewrite. The same rewrite one degree down turns (px + q)/(bx + c) into the constant p/b plus a remainder over the divisor. Both are guarded on the leading coefficient being a non-zero number, since the rewrite divides by it. A quadratic denominator with a vanishing quadratic term falls through to the linear arm rather than being divided by zero. Also 1/cos(u)^2 and 1/sin(u)^2, which are written that way at least as often as sec(u)^2 and csc(u)^2, and none of the four shapes was recognised. Corpus 88/117 -> 91/117: int:table and int:rational-quadratic reach 100%, and int:partial-fractions goes from 2 out of 5 to 3. 3901 unit tests and 127 F# tests pass, no wrong answers in the corpus, and the property checker's 1320 checks are clean. x^2/(x^4 + 1) and 1/(x^3 + 1) are still out of reach: both need the denominator factored first, which is a different piece of work.
1 parent 004e3a4 commit 47ba93b

2 files changed

Lines changed: 129 additions & 1 deletion

File tree

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ _ when TryReadSineCosinePowers(expr, x, out var trigArg, out var sinePower, out
6666
(arg / a) * (MathS.Ln(MathS.Abs(arg)) - 1),
6767

6868
Entity.Divf(var numerator, var denominator) when
69-
!numerator.ContainsNode(x)
69+
!numerator.ContainsNode(x)
7070
&& TreeAnalyzer.TryGetPolyQuadratic(denominator, x, out var a, out var b, out var c) // ∫ k/(ax^2 + bx + c) dx
7171
=> IntegrateRationalQuadratic(numerator, a, b, c, x),
7272

@@ -132,6 +132,42 @@ _ when TryReadSineCosinePowers(expr, x, out var trigArg, out var sinePower, out
132132
&& TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var ra, out var rb, out var rc)
133133
=> IntegrateOverRootOfQuadratic(numerator, ra, rb, rc, radicand, x),
134134

135+
// ∫ (px + q)/(ax^2 + bx + c) dx. Only the constant numerator was covered, so
136+
// x/(x^2 + 2x + 5) and (x + 3)/(x^2 + 3x + 2) had no antiderivative at all.
137+
Entity.Divf(var numerator, var denominator) when
138+
numerator.ContainsNode(x)
139+
&& TreeAnalyzer.TryGetPolyLinear(numerator, x, out var p, out var q)
140+
&& TreeAnalyzer.TryGetPolyQuadratic(denominator, x, out var a, out var b, out var c)
141+
&& a.Evaled is Entity.Number.Complex { IsZero: false }
142+
=> IntegrateLinearOverQuadratic(p, q, a, b, c, denominator, x),
143+
144+
// ∫ (px + q)/(bx + c) dx, the same rewrite one degree down: the quotient is the
145+
// constant p/b plus a remainder over the divisor. x/(x + 1) had no antiderivative.
146+
Entity.Divf(var numerator, var denominator) when
147+
numerator.ContainsNode(x)
148+
&& TreeAnalyzer.TryGetPolyLinear(numerator, x, out var p, out var q)
149+
&& TreeAnalyzer.TryGetPolyLinear(denominator, x, out var b, out var c)
150+
&& b.Evaled is Entity.Number.Complex { IsZero: false }
151+
=> p * x / b + (q - p * c / b) * MathS.Ln(MathS.Abs(denominator)) / b,
152+
153+
// 1/cos(u)^2 and 1/sin(u)^2, which are written that way at least as often as
154+
// sec(u)^2 and csc(u)^2 and were not recognised in that form.
155+
Entity.Divf(var numerator, Entity.Powf(Entity.Cosf(var arg), Entity.Number.Integer(2))) when
156+
!numerator.ContainsNode(x) && TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
157+
numerator * MathS.Tan(arg) / a,
158+
159+
Entity.Divf(var numerator, Entity.Powf(Entity.Sinf(var arg), Entity.Number.Integer(2))) when
160+
!numerator.ContainsNode(x) && TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
161+
-numerator * MathS.Cotan(arg) / a,
162+
163+
Entity.Powf(Entity.Secantf(var arg), Entity.Number.Integer(2)) when
164+
TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
165+
MathS.Tan(arg) / a,
166+
167+
Entity.Powf(Entity.Cosecantf(var arg), Entity.Number.Integer(2)) when
168+
TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
169+
-MathS.Cotan(arg) / a,
170+
135171
_ => null
136172
};
137173

@@ -311,6 +347,17 @@ private static Entity IntegrateOverRootOfQuadratic(
311347
]);
312348
}
313349

350+
/// <summary>
351+
/// ∫ (px + q)/(ax^2 + bx + c) dx, by writing the numerator as a multiple of the
352+
/// denominator's derivative plus a constant:
353+
/// px + q = (p/2a)(2ax + b) + (q - pb/2a). The first part integrates to a logarithm
354+
/// and the second is the constant-numerator case above.
355+
/// </summary>
356+
private static Entity IntegrateLinearOverQuadratic(
357+
Entity p, Entity q, Entity a, Entity b, Entity c, Entity denominator, Entity.Variable x)
358+
=> p / (2 * a) * MathS.Ln(MathS.Abs(denominator))
359+
+ IntegrateRationalQuadratic(q - p * b / (2 * a), a, b, c, x);
360+
314361
private static Entity IntegrateRationalQuadratic(Entity numerator, Entity a, Entity b, Entity c, Entity.Variable x)
315362
{
316363
// The formula depends on whether it's linear (a = 0) or quadratic (a ≠ 0)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// Copyright (c) 2019-2022 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 AngouriMath;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// Quotients of polynomials of low degree. Only a constant numerator was recognised, so
16+
/// x/(x^2 + 2x + 5) and x/(x + 1) had no antiderivative at all. Each answer is checked by
17+
/// differentiating it back and comparing at points, since what matters is that it is an
18+
/// antiderivative and not what form it is written in.
19+
/// </summary>
20+
public sealed class RationalIntegralsTest
21+
{
22+
private static void AssertIsAntiderivative(string integrand, params double[] points)
23+
{
24+
var f = integrand.ToEntity();
25+
var antiderivative = f.Integrate("x");
26+
Assert.DoesNotContain("integral(", antiderivative.Stringize());
27+
var derivative = antiderivative.Substitute("C", 0).Differentiate("x");
28+
foreach (var point in points)
29+
{
30+
var expected = f.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble();
31+
var actual = derivative.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble();
32+
Assert.Equal(expected, actual, 8);
33+
}
34+
}
35+
36+
// (px + q) / (ax^2 + bx + c). The numerator is written as a multiple of the
37+
// denominator's derivative plus a constant, which splits it into a logarithm and the
38+
// constant-numerator case that was already there.
39+
[Theory]
40+
[InlineData("x / (x ^ 2 + 2 * x + 5)", new[] { 0.3, 1.7, -0.6 })]
41+
[InlineData("(x + 3) / (x ^ 2 + 3 * x + 2)", new[] { 0.3, 1.7, 4.2 })]
42+
[InlineData("(2 * x + 1) / (x ^ 2 + x + 1)", new[] { 0.3, 1.7, -0.6 })]
43+
[InlineData("x / (x ^ 2 + 1)", new[] { 0.3, 1.7, -0.6 })]
44+
[InlineData("(3 * x - 2) / (2 * x ^ 2 + 5)", new[] { 0.3, 1.7, -0.6 })]
45+
public void ALinearNumeratorOverAQuadratic(string integrand, double[] points) =>
46+
AssertIsAntiderivative(integrand, points);
47+
48+
// (px + q) / (bx + c), the same rewrite one degree down.
49+
[Theory]
50+
[InlineData("x / (x + 1)", new[] { 0.3, 1.7 })]
51+
[InlineData("(2 * x + 1) / (x - 3)", new[] { 0.3, 1.7 })]
52+
[InlineData("(3 * x) / (2 * x + 5)", new[] { 0.3, 1.7 })]
53+
public void ALinearNumeratorOverALinearDenominator(string integrand, double[] points) =>
54+
AssertIsAntiderivative(integrand, points);
55+
56+
// 1/cos(u)^2 and 1/sin(u)^2 are written that way at least as often as sec(u)^2 and
57+
// csc(u)^2, and neither of the four shapes was recognised.
58+
[Theory]
59+
[InlineData("1 / cos(x) ^ 2", new[] { 0.3, 1.1, -0.6 })]
60+
[InlineData("1 / sin(x) ^ 2", new[] { 0.3, 1.1, 2.2 })]
61+
[InlineData("2 / cos(3 * x) ^ 2", new[] { 0.3, 0.9 })]
62+
[InlineData("sec(x) ^ 2", new[] { 0.3, 1.1, -0.6 })]
63+
[InlineData("cosec(x) ^ 2", new[] { 0.3, 1.1, 2.2 })]
64+
public void ReciprocalSquaresOfTheWaves(string integrand, double[] points) =>
65+
AssertIsAntiderivative(integrand, points);
66+
67+
// The shapes these sit next to in the table have to keep working. A constant over a
68+
// quadratic in particular is matched by the earlier arm and must stay there.
69+
[Theory]
70+
[InlineData("1 / (x ^ 2 + 1)", new[] { 0.3, 1.7, -0.6 })]
71+
[InlineData("1 / (x ^ 2 + 2 * x + 5)", new[] { 0.3, 1.7, -0.6 })]
72+
[InlineData("1 / (2 * x + 3)", new[] { 0.3, 1.7 })]
73+
[InlineData("1 / x", new[] { 0.3, 1.7 })]
74+
[InlineData("x ^ 2", new[] { 0.3, 1.7 })]
75+
[InlineData("sin(x)", new[] { 0.3, 1.7 })]
76+
[InlineData("tan(x)", new[] { 0.3, 1.1 })]
77+
[InlineData("x * e ^ x", new[] { 0.3, 1.7 })]
78+
public void NeighbouringFormsAreUnaffected(string integrand, double[] points) =>
79+
AssertIsAntiderivative(integrand, points);
80+
}
81+
}

0 commit comments

Comments
 (0)