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 @@ -312,6 +312,20 @@ an arm for each sign of a quantity that is not real, `x^2 provided not x = 0` be
| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().InnerSimplified` | `NaN` | `2 * x` where `x` is not 0 |
| `"piecewise(1 provided i < 0, 2 provided i > 0)".ToEntity().InnerSimplified` | `NaN` | `NaN`, as before: no case holds |

### A power of x times a power of a function of another power of x is integrated under that power

**Answers where there were none.** `x^3 (a + b atanh(c x^2))^2` ran out of time, and so did ten more of Rubi's 3.4, 5.3.2
and 7.3.2. `x^m F(x^n)^p`, with `F` a sum holding an inverse tangent, inverse hyperbolic tangent or logarithm of
`x^n` and `p` at least 2, is now integrated under `t = x^n` wherever `(m + 1)/n` is whole
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^3*(a+b*atanh(c*x^2))^2".ToEntity().Integrate("x")` | `integral(...)` | 308 characters |
| `"(a+b*atan(c*x^2))^2/x^5".ToEntity().Integrate("x")` | `integral(...)` | 870 characters |
| `"(a+b*atanh(c*x^(3/2)))^2/x^4".ToEntity().Integrate("x")` | `integral(...)` | 330 characters |
| `"(a+b*ln(c*(d+k/x^(2/3))^n))^2/x^5".ToEntity().Integrate("x")` | `integral(...)` | 2,034 characters |

### Zero times a power with a non-real exponent is zero where the power is defined

**A value where there was NaN.** `0 b^i` simplified to NaN, and so did `b^i - b^i`. That in turn made `x b^i`'s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26286,6 +26286,67 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A power of x times a whole power, past the first, of a sum holding an inverse function or a
/// logarithm of a power of x, <c>x^m (a + b atanh(c x^n))^p</c>, under <c>t = x^n</c> where
/// <c>(m + 1)/n</c> is whole: <c>x^m dx = t^((m + 1)/n - 1) dt/n</c>.
/// </summary>
/// <remarks>
/// The rules before it timed out on Rubi's 5.3.2 <c>(a + b atan(c x^2))^2/x^5</c> and 7.3.2
/// <c>x^3 (a + b atanh(c x^2))^2</c>, <c>(a + b atanh(c/x^2))^2/x^5</c> and
/// <c>(a + b atanh(c x^(3/2)))^2/x^4</c>, each a problem in <c>t</c> they answer in a second.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(Entity expr, Entity.Variable x)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
// The factors of a product and a quotient, each with the sign of its exponent.
static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch
{
Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)),
Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)),
_ => new[] { (e, below) }
};
ERational m = ERational.Zero;
Entity? function = null;
Entity constant = Number.Integer.One;
foreach (var (factor, below) in Factors(expr, false))
{
if (!factor.ContainsNode(x))
constant = below ? constant / factor : constant * factor;
else if (factor == x)
m = below ? m - ERational.One : m + ERational.One;
else if (factor is Powf(var b0, Number.Rational { ERational: var r }) && b0 == x)
m = below ? m - r : m + r;
else if (function is null && !below && factor is Powf(var sum, Number.Integer { EInteger: var power }) && power.CompareTo(EInteger.FromInt32(2)) >= 0)
function = factor;
else
return null;
}
if (function is null)
return null;
// The one power of x the function is written in, and an inverse function or a logarithm of it.
var powers = function.Nodes.Where(node => node is Powf(var b1, Number.Rational) && b1 == x).Distinct().ToList();
if (powers.Count != 1 || powers[0] is not Powf(_, Number.Rational { ERational: var n }) || n.IsZero || n.Equals(ERational.One))
return null;
var t = Variable.CreateUnique(expr, "t_pow");
var inT = function.Substitute(powers[0], t);
if (inT.ContainsNode(x)
|| !inT.Nodes.Any(node => node is Arcsinf or Arccosf or Arctanf or Arccotanf or Logf && node.ContainsNode(t)))
return null;
// PeterO keeps a quotient as written: -4/2 has the denominator 2 until it is reduced.
var k = ((m + ERational.One) / n).ToLowestTerms();
if (!k.Denominator.Equals(EInteger.One) || !k.Numerator.CanFitInInt32())
return null;
var exponent = k.Numerator.ToInt32Unchecked() - 1;
var inTWhole = (exponent == 0 ? inT : MathS.Pow(t, Number.Integer.Create(exponent)) * inT).InnerSimplified;
if (Integration.ComputeAsAQuestionOfItsOwn(inTWhole, t, integrateByParts: true) is not { } integrated)
return null;
var answer = (constant / Number.Rational.Create(n.Numerator, n.Denominator) * integrated.Substitute(t, powers[0])).InnerSimplified;
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol
/// among the coefficients: <c>sin(x)/(a + b sin(x) + c sin(x)^2)</c>, and the same with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the
// binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer;
// A power of x times a power of a sum holding an inverse function of x^n, under t = x^n,
// before the rules that timed out on `x^3 (a + b atanh(c x^2))^2`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(expr, x)) is { }) return answer;
// A polynomial in a sine or a cosine over a whole power of a binomial in it, in powers of the
// binomial, before the half-angle wrote 30 million characters for `(A + B cos + C cos^2)/(a + b cos)^4`.
if ((answer = IndefiniteIntegralSolver.SolveAPolynomialInASineOverAPowerOfABinomialInItInPowersOfTheBinomial(expr, x)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// 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 power of x times a power of a sum holding an inverse function of another power of x, under
/// <c>t = x^n</c>: Rubi's 5.3.2 and 7.3.2 <c>x^m (a + b atanh(c x^n))^2</c>, which timed out.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfXTimesAFunctionOfAnotherIntegralTest
{
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

var pinned = integral.Substitute("C", 0);
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
pinned = pinned.Substitute(name, value);
original = original.Substitute(name, value);
}
var derivative = pinned.Differentiate("x");
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

[Theory]
[InlineData("(a+b*atan(c*x^2))^2/x^5")]
[InlineData("x^3*(a+b*atanh(c*x^2))^2")]
[InlineData("(a+b*atanh(c/x^2))^2/x^5")]
[InlineData("x^11*(a+b*atanh(c*x^3))^2")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 0.3));
DifferentiatesBack(integrand, ("a", -2), ("b", 1.5), ("c", -0.2));
}
}
}
Loading