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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@ since semantic versioning admits it.
Measured on the suite: **7 of 8,525 tests** referenced the old reading — five boolean-solver inputs
written `A | B`, and two that existed to record this spelling as at risk.

### A power of x times `atanh(sqrt(e) x/sqrt(d + e x^2))` is integrated by parts against the root

**Answers where there were none.** `x atanh(sqrt(e) x/sqrt(d + e x^2))` was declined, and so were twelve more of Rubi's
7.3.7, every power of `x` from `x^6` to `1/x^9` but `1/x`. The logarithm `atanh` is written as has the derivative
`sqrt(e)/sqrt(d + e x^2)`, and by parts what is left is `x^(m + 1)/sqrt(d + e x^2)`; differentiated as written it was a
quotient of two sums with a root in each, which the search never brought down to the root
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"atanh(x*sqrt(k)/sqrt(d+k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | `x atanh(...) - sqrt(d + k x^2)/sqrt(k)`, written in the logarithm |
| `"atanh(x*sqrt(k)/sqrt(d+k*x^2))/x^5".ToEntity().Integrate("x")` | `integral(...)` | by parts onto `1/(x^4 sqrt(d + k x^2))` |

### A power of x is taken out of a bracket to find the derivative of a product of powers

**Answers where there were none.** `x^(2n) (c + d x)^n (2 c x + 3 d x^2)`, Rubi's 1.3.1, was declined. A bracket that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,76 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
return Integration.ComputeAsTheSameQuestion(written, x, integrateByParts);
}

/// <summary>
/// A power of <c>x</c> times <c>artanh(A x/sqrt(d + B x^2))</c> with <c>A^2 = B</c>, by parts:
/// the logarithm <c>ln((1 + u)/(1 - u))</c> that <c>artanh(u)</c> is written as has the
/// derivative <c>2 u'/(1 - u^2)</c>, and for this <c>u</c>, <c>1 - u^2 = d/(d + B x^2)</c> and
/// <c>u' = A d/(d + B x^2)^(3/2)</c>, so it is <c>2 A/sqrt(d + B x^2)</c>. So
/// <c>integral x^m ln(...) = x^(m + 1)/(m + 1) ln(...) - 2 A/(m + 1) integral x^(m + 1)/sqrt(d + B x^2)</c>,
/// for every whole <c>m</c> but <c>-1</c>. Rubi's 7.3.7, <c>x^m artanh(sqrt(e) x/sqrt(d + e x^2))</c>.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </summary>
/// <remarks>
/// The search takes it by parts too, and differentiates the logarithm as written: a quotient
/// of two sums with a root in each, which it never brought down to the root alone, and the
/// remainder it asked about ran past the budget. The identity is algebraic, with no branch in
/// it: it holds wherever <c>d</c> is not 0 and the root is.
/// </remarks>
internal static Entity? SolveByPartsAgainstAnArtanhWhoseDerivativeIsARoot(Entity expr, Entity.Variable x, bool integrateByParts)
{
Entity constant = Number.Integer.One;
Logf? logarithm = null;
var power = 0;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
{
if (!factor.ContainsNode(x))
{
constant = underneath ? constant / factor : constant * factor;
continue;
}
if (!underneath && logarithm is null && factor is Logf ln && ln.Base == MathS.e)
{
logarithm = ln;
continue;
}
if (APowerOfTheVariable(factor, x) is not { } k)
return null;
power += underneath ? -k : k;
}
if (logarithm is null || power == -1 || System.Math.Abs(power) > 24
|| logarithm.Antilogarithm is not Divf(Sumf(var one, var u), Minusf(var alsoOne, var alsoU))
|| one != Number.Integer.One || alsoOne != Number.Integer.One || u != alsoU)
return null;
// u read as A x/sqrt(Q): a constant, x once above the bar, and the root of Q.
Entity slope = Number.Integer.One;
Entity? radicand = null;
var sawX = false;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(u))
{
if (!factor.ContainsNode(x))
slope = underneath ? slope / factor : slope * factor;
else if (factor == x && !underneath && !sawX)
sawX = true;
else if (radicand is null && factor is Powf(var @base, Number.Rational half)
&& (underneath ? half == Number.Rational.Create(1, 2) : half == Number.Rational.Create(-1, 2)))
radicand = @base;
else
return null;
}
if (!sawX || radicand is null
|| !TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var square, out var linear, out var free)
|| !TreeAnalyzer.IsZero(linear) || TreeAnalyzer.IsZero(free) || !VanishesIdentically(slope * slope - square))
return null;
var raised = power + 1;
var rest = raised == 0 ? 1 / MathS.Sqrt(radicand)
: raised > 0 ? (raised == 1 ? x : MathS.Pow(x, raised)) / MathS.Sqrt(radicand)
: 1 / ((raised == -1 ? x : MathS.Pow(x, -raised)) * MathS.Sqrt(radicand));
if (Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts) is not { } integral
|| integral.Nodes.Any(node => node is Integralf || node == MathS.NaN))
return null;
return (constant * (MathS.Pow(x, raised) * logarithm - 2 * slope * integral) / raised).InnerSimplified;
}

/// <summary>
/// A quotient by the square of a base, by parts against the base's reciprocal, where the
/// base's derivative makes the rest exact: <c>N/(M v^2)</c> is <c>g v'/v^2</c> with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAnExponentialOfAQuadraticInALogarithm(expr, x)) is { }) return answer;
// And a negative whole power of one, onto the exponential integral by t = A + B ln(c x^r).
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverAPowerOfALogarithm(expr, x)) is { }) return answer;
// And x^m artanh(A x/sqrt(d + A^2 x^2)), by parts, the logarithm's derivative read as the root it is.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstAnArtanhWhoseDerivativeIsARoot(expr, x, integrateByParts)) is { }) return answer;
// A power of an exponential with a positive base is the exponential of the product,
// exactly, and only that spelling is one the exponential rules read.
if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(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>
/// <c>x^m artanh(sqrt(k) x/sqrt(d + k x^2))</c>, whose derivative is <c>sqrt(k)/sqrt(d + k x^2)</c>,
/// by parts. Differentiated as written, the logarithm is a quotient of two sums with a root in
/// each, and the search ran past its budget on what was left. Rubi's 7.3.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerTimesAnArtanhWhoseDerivativeIsARootIntegralTest
{
[Theory]
[InlineData("atanh(x*sqrt(k)/sqrt(d + k*x^2))")]
[InlineData("x*atanh(x*sqrt(k)/sqrt(d + k*x^2))")]
[InlineData("x^4*atanh(x*sqrt(k)/sqrt(d + k*x^2))")]
[InlineData("atanh(x*sqrt(k)/sqrt(d + k*x^2))/x^2")]
[InlineData("atanh(x*sqrt(k)/sqrt(d + k*x^2))/x^5")]
[InlineData("atanh(x/sqrt(1 + x^2))")]
public void ByPartsAgainstTheRoot(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Entity Pinned(Entity e) => e.Substitute("d", 1.3).Substitute("k", 0.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -2.1, -0.4, 0.3, 0.8, 1.3, 2.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading