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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ Rubi's `x^m (a + b x^n)^p` and `(a + b x^n)^p (c + d x^n)^q`
| `"1/(2+3/x^2)^3".Integrate("x")`, `1/(a + b/x^3)` | left unevaluated | an antiderivative |
| `"(a+b*x^n)*(c+d*x^n)^3".Integrate("x")` | left unevaluated | written out, eight powers of `x` integrated: `a c^3 x + ... + b d^3 x^(4 n + 1)/(4 n + 1)` |

### A polynomial with symbols in it that is a binomial or an even quartic in a shifted variable is integrated in it

**Answers where there were none.** `1/(c^2 x^3 + 3 b c x^2 + 3 b^2 x + 3 a b)` was left unevaluated,
although its denominator is `((c x + b)^3 + 3 a b c - b^3)/c`, which the rule for a binomial reads at
once: nothing factors a polynomial with a symbol among its coefficients. A polynomial below the bar
of the third to the sixth degree, with a symbol in it, that written in `y = x + s` with
`s = a_(n-1)/(n a_n)` is a binomial, or a quartic even in `y`, is integrated in `y`. Rubi's 1.3.1
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(3*a*b + 3*b^2*x + 3*b*c*x^2 + c^2*x^3)".ToEntity().Integrate("x")`, and its square | `integral(...)` | two logarithms and an arctangent in `x + b/c` |
| `"x/(a + 8*x - 8*x^2 + 4*x^3 - x^4)".ToEntity().Integrate("x")`, and `1` over it | `integral(...)` | the antiderivative in `x - 1` |

### The hyperbolic functions have antiderivatives, and so does anything rational in `e^(k x)`

An integrand rational in `e^(k x)` becomes a rational function of one variable under
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,17 @@ is var (multiple, leftover)
?? Integration.ComputeIndefiniteIntegral(numerator / respelled, x, integrateByParts)) is { } overPolynomials)
return overPolynomials;

// A polynomial below the bar that is a binomial, or a quartic even in its variable, once
// written in `y = x + s` with `s = a_(n-1)/(n a_n)`: `c^2 x^3 + 3 b c x^2 + 3 b^2 x + 3 a b`
// is `((c x + b)^3 + 3 a b c - b^3)/c`, and `a + 8 x - 8 x^2 + 4 x^3 - x^4` is
// `a + 3 - 2 y^2 - y^4` in `y = x - 1`. Nothing above factors a polynomial with a symbol
// in it, and the rules for a binomial and for an even quartic read it in y at once.
// Once: in y the term that s takes away is gone.
if (InTheVariableThatDepressesIt(numerator, denominator, x) is var (inY, y, shift)
&& (SolveByPartialFractions(inY, y, integrateByParts)
?? Integration.ComputeIndefiniteIntegral(inY, y, integrateByParts)) is { } inTheShiftedVariable)
return inTheShiftedVariable.Substitute(y, (x + shift).InnerSimplified);

// A denominator with a written repeated factor takes the Hermite reduction first:
// the rational part of the answer in one linear solve, and what is left is a proper
// fraction over a squarefree denominator for the splits below. `(1 + x^2)/(x (1 + x^3)^2)`
Expand Down Expand Up @@ -850,6 +861,79 @@ is var (multiple, leftover)
return null;
}

/// <summary>
/// <paramref name="numerator"/> over <paramref name="denominator"/> written in <c>y = x + s</c>,
/// where the denominator is a constant times a power of one polynomial in x of the third to
/// the sixth degree, written as a sum of its monomials, that in y is a binomial or, of the
/// fourth degree, even; <c>s = a_(n-1)/(n a_n)</c> is what takes its term of degree
/// <c>n - 1</c> away. Null otherwise, and where that term is not there to take away.
/// </summary>
private static (Entity InY, Entity.Variable Y, Entity Shift)? InTheVariableThatDepressesIt(Entity numerator, Entity denominator, Entity.Variable x)
{
Entity? polynomial = null;
var power = 0;
Entity constant = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(denominator))
{
if (!factor.ContainsNode(x))
{
constant *= factor;
continue;
}
if (polynomial is not null)
return null;
(polynomial, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent) && exponent.EInteger.CanFitInInt32()
? (raised, exponent.EInteger.ToInt32Unchecked()) : (factor, 1);
}
// With a symbol among its coefficients, since a polynomial over the rationals is split
// over its factors above; and written out, since one written in a linear,
// `a + (b + c x)^3`, is read as it stands.
if (polynomial is null || !polynomial.Vars.Any(symbol => symbol != x)
|| polynomial.Nodes.Any(node => node is Powf(Sumf or Minusf, _) && node.ContainsNode(x))
|| !TreeAnalyzer.TryGetPolynomial(polynomial, x, out var terms)
|| terms.Any(term => term.Key.Sign < 0 || !term.Key.CanFitInInt32() || term.Value.ContainsNode(x)))
return null;
if (numerator.ContainsNode(x)
&& (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var above) || above.Any(term => term.Key.Sign < 0 || term.Value.ContainsNode(x))))
return null;
var degree = terms.Keys.Max()!.ToInt32Unchecked();
if (degree < 3 || degree > 6)
return null;
Entity Coefficient(int k) => terms.TryGetValue(EInteger.FromInt32(k), out var coefficient) ? coefficient : Number.Integer.Zero;
if (VanishesIdentically(Coefficient(degree)) || VanishesIdentically(Coefficient(degree - 1)))
return null;
var shift = Functions.PartialFractions.InLowestTermsOverTheSymbols(Coefficient(degree - 1) / (degree * Coefficient(degree)));
// P(y - s) has at y^i the sum over m >= i of a_m C(m, i) (-s)^(m - i).
var inY = new Entity[degree + 1];
for (var i = 0; i <= degree; i++)
{
Entity sum = Number.Integer.Zero;
var choose = EInteger.One;
for (var m = i; m <= degree; m++)
{
if (m > i)
choose = choose.Multiply(EInteger.FromInt32(m)).Divide(EInteger.FromInt32(m - i));
sum += Coefficient(m) * Number.Integer.Create(choose) * (m == i ? Number.Integer.One : MathS.Pow(-shift, m - i));
}
inY[i] = Functions.PartialFractions.InLowestTermsOverTheSymbols(sum);
}
var vanishing = inY.Select(VanishesIdentically).ToArray();
var aBinomial = Enumerable.Range(1, degree - 1).All(i => vanishing[i]);
var anEvenQuartic = degree == 4 && vanishing[1] && vanishing[3];
if (!aBinomial && !anEvenQuartic)
return null;
var y = Variable.CreateUnique(numerator + denominator, "y_shift");
Entity depressed = Number.Integer.Zero;
for (var i = degree; i >= 0; i--)
if (!vanishing[i])
{
var monomial = i == 0 ? inY[i] : inY[i] * (i == 1 ? y : MathS.Pow(y, i));
depressed = depressed == Number.Integer.Zero ? monomial : depressed + monomial;
}
var below = power == 1 ? depressed : MathS.Pow(depressed, power);
return (numerator.Substitute(x, y - shift) / (constant == Number.Integer.One ? below : constant * below), y, shift);
}

/// <summary>
/// A polynomial over a power of a linear, <c>P(x)/(a + b x)^k</c> with <c>k >= 2</c>,
/// under <c>t = a + b x</c>: <c>P((t - a)/b) t^(-k) / b</c> is a sum of powers of
Expand Down
61 changes: 61 additions & 0 deletions Sources/Tests/UnitTests/Calculus/ShiftedPolynomialIntegralTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using System;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A polynomial below the bar with a symbol among its coefficients that is a binomial, or a
/// quartic even in its variable, once written in <c>y = x + s</c> with
/// <c>s = a_(n-1)/(n a_n)</c>: Rubi's 1.3.1. Nothing factors a polynomial with a symbol in it,
/// and in y the rules for a binomial and for an even quartic read it at once.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back with the symbols pinned after the integral is taken.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class ShiftedPolynomialIntegralTest
{
private static readonly double[] Points = { 0.3, 0.6, 0.9 };

private static void DifferentiatesBack(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 0.7).Substitute("b", 1.3).Substitute("c", 2.1);
var derivative = Pin(integral.Substitute("C", 0).Differentiate("x"));
var original = Pin(integrand.ToEntity());
foreach (var point in Points)
{
var expected = original.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble();
var actual = derivative.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble();
Assert.True(Math.Abs(expected - actual) < 1e-8 * Math.Max(1, Math.Abs(expected)),
$"d/dx of the antiderivative of {integrand} is {actual} at x = {point}, where the integrand is {expected}");
}
}

/// <summary>
/// <c>c^2 x^3 + 3 b c x^2 + 3 b^2 x + 3 a b</c> is <c>((c x + b)^3 + 3 a b c - b^3)/c</c>.
/// </summary>
[Theory]
[InlineData("1/(3*a*b + 3*b^2*x + 3*b*c*x^2 + c^2*x^3)")]
[InlineData("1/(3*a*b + 3*b^2*x + 3*b*c*x^2 + c^2*x^3)^2")]
public void ACubicThatIsABinomialInALinear(string integrand) => DifferentiatesBack(integrand);

/// <summary>
/// <c>a + 8 x - 8 x^2 + 4 x^3 - x^4</c> is <c>a + 3 - 2 y^2 - y^4</c> in <c>y = x - 1</c>.
/// </summary>
[Theory]
[InlineData("x/(a + 8*x - 8*x^2 + 4*x^3 - x^4)")]
[InlineData("1/(a + 8*x - 8*x^2 + 4*x^3 - x^4)")]
public void AQuarticThatIsEvenInALinear(string integrand) => DifferentiatesBack(integrand);
}
}
Loading