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
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,25 @@ 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 whole power of a quotient with a symbol in it is integrated as the quotient of the powers

**Answers where there were none, and a slowdown since 2.5.0 undone.** `(c/(a + c x^2))^2` was
declined, after twenty-six seconds, while `c^2/(a + c x^2)^2`, the same function, was answered at
once: the table reads the quotient of the powers and nothing read the power of the quotient. It is
written so now wherever the quotient is of two polynomials in `x` with a symbol in them and what
it leaves above the bar is a monomial, the table's `x^m/(a + b x^n)^p`; that is exact for a whole
power. That is
also how the substitution `u = x^2` writes `x/(a + c x^4)^2` since 2.5.0, having simplified
`1/(a/c + u^2)^2`, and that integrand took twenty seconds where 2.5.0 took a tenth of one
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(c/(a + c*x^2))^2".ToEntity().Integrate("x")` | `integral(...)`, after a minute | a quotient and an arctangent or a logarithm, by the sign of `a c` |
| `"(1/(a + c*x^2))^2".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"(x/(a + c*x^2))^2".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"x/(a + c*x^4)^2".ToEntity().Integrate("x")` | the answer, in 0.1 s; 22 s on the unreleased master | the answer, in 0.06 s |

### 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17021,6 +17021,44 @@ node is Powf(Mulf(var l, var r) product, Number.Integer power) && product.Contai
return distributed == expr ? null : Integration.ComputeAsTheSameQuestion(distributed, x, integrateByParts);
}

/// <summary>
/// A whole power of a quotient with a symbol in it, written as the quotient of the powers:
/// <c>(c/(a + c u^2))^2</c> is <c>c^2/(a + c u^2)^2</c>, exact for a whole power. That is how
/// the substitution <c>u = x^2</c> writes <c>x/(a + c x^4)^2</c>, having simplified
/// <c>1/(a/c + u^2)^2</c>, and the table reads the quotient of the powers and not the power
/// of the quotient: <c>c^2/(a + c x^2)^2</c> was answered in a few milliseconds where
/// <c>(c/(a + c x^2))^2</c> went to parts and was declined after twenty-six seconds, and
/// <c>x/(a + c x^4)^2</c> was answered after forty, by another route. With numbers only it
/// is answered as it is already. Only a quotient of two polynomials in <paramref name="x"/>:
/// the hyperbolic functions arrive as quotients of exponentials, and the imaginary tangent's
/// sum as <c>A e^(i z)/cos(z)</c>, and written apart those went to slower routes --
/// <c>cosh(c + d x)/(a + b tanh(c + d x)^2)</c> from 17 ms to past five seconds. And only with
/// a monomial left above the bar, the table's <c>x^m/(a + b x^n)^p</c>: a sum there is
/// multiplied out by the rational rules, and the folded <c>tanh(a + 2 ln(x))^2</c>,
/// <c>((e^(2a) x^4 - 1)/(e^(2a) x^4 + 1))^2</c>, went from five seconds to forty-five that way.
/// Asked as the same question.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </summary>
internal static Entity? SolveByDistributingWholePowersOfQuotients(Entity expr, Entity.Variable x, bool integrateByParts)
{
var distributed = expr.Replace(node =>
node is Powf(Divf(var above, var below) quotient, Number.Integer power) && quotient.ContainsNode(x)
&& quotient.Vars.Any(symbol => symbol != x)
&& IsAPolynomialIn(above, x) && IsAPolynomialIn(below, x)
&& IsAMonomialIn(power.EInteger.Sign > 0 ? above : below, x)
? power.EInteger.Sign > 0
? MathS.Pow(above, power) / MathS.Pow(below, power)
: MathS.Pow(below, Number.Integer.Create(power.EInteger.Negate())) / MathS.Pow(above, Number.Integer.Create(power.EInteger.Negate()))
: node);
return distributed == expr ? null : Integration.ComputeAsTheSameQuestion(distributed, x, integrateByParts);

static bool IsAPolynomialIn(Entity expr, Entity.Variable x)
=> TreeAnalyzer.TryGetPolynomial(expr, x, out var terms)
&& terms.Keys.All(power => power.Sign >= 0) && terms.Values.All(coefficient => !coefficient.ContainsNode(x));
static bool IsAMonomialIn(Entity expr, Entity.Variable x)
=> TreeAnalyzer.TryGetPolynomial(expr, x, out var terms) && terms.Count == 1;
}

/// <summary>
/// A polynomial in <c>x</c> times a rational function of exponentials of <c>x</c>,
/// by parts against the whole rational function: <c>x tanh(x)^2</c> is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// A whole power of a product of a constant and the variable, as the product of
// the powers, which is how the inverse hyperbolic secant and cosecant arrive.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingWholePowersOfProducts(expr, x, integrateByParts)) is { }) return answer;
// And of quotients, which a substitution leaves where it has simplified a sum over
// the bar: `(c/(a + c u^2))^2` is read by nothing that reads `c^2/(a + c u^2)^2`.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingWholePowersOfQuotients(expr, x, integrateByParts)) is { }) return answer;
// And a fractional or symbolic power of a monomial: `(c x^n)^b` is `c^b x^(n b)`
// for a positive `c`, on the `x > 0` where a symbolic `n` leaves the integrand real.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingAPowerOfAMonomial(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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 whole power of a quotient with a symbol in it is the quotient of the powers:
/// <c>(c/(a + c x^2))^2</c> is <c>c^2/(a + c x^2)^2</c>, which the table answers, and was
/// declined. It is how the substitution <c>u = x^2</c> writes <c>x/(a + c x^4)^2</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back with <c>a = 2.3</c>, <c>b = 0.7</c>, <c>c = 1.3</c>, on
/// both sides of zero.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class WholePowerOfAQuotientIntegralTest
{
[Theory]
[InlineData("(c/(a + c*x^2))^2")]
[InlineData("(1/(a + c*x^2))^2")]
[InlineData("(x/(a + c*x^2))^2")]
[InlineData("(c/(a + c*x^2))^3")]
[InlineData("((a + c*x^2)/x)^(-2)")]
[InlineData("x/(a + c*x^4)^2")]
[InlineData("x^2/(a + b*x^6)^2")]
public void IsTheQuotientOfThePowers(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 2.3).Substitute("b", 0.7).Substitute("c", 1.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.4, -0.6, 0.5, 1.2 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}
}
}
Loading