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
12 changes: 12 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ quotient of two sums with a root in each, which the search never brought down to
| `"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 sine over x and a power of a binomial has the power lowered first

**Answers where there were none.** Rubi's 4.1.11 `sin(c + d x)/(x (a + b x^3)^3)` was declined. The split over x and the
cube's three roots, each to the third power, came out past the size it hands on. Since `a + b x^3 - b x^3` is `a`,
`1/(x Q^k)` is `1/(a x Q^(k - 1)) - (b/a) x^2/Q^k`, and each of the two is answered as it stands
([#1501](https://github.com/asc-community/AngouriMath/issues/1501)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sin(c+d*x)/(x*(a+b*x^3)^3)".ToEntity().Integrate("x")` | `integral(...)` | 13,110 characters |
| `"cos(c+d*x)/(x*(a+b*x^3)^3)".ToEntity().Integrate("x")` | `integral(...)` | 13,132 characters |

### A power of x times the arsech or arcsch of `sqrt(x)` is integrated by parts

**Answers where there were none.** Every power from `x^0` to `x^3` times `asech(sqrt(x))` or `acsch(sqrt(x))`, Rubi's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11150,10 +11150,53 @@ void Add(int k, bool isSine, Entity coefficient)
if (expr is not (Divf or Mulf or Powf or Sinf or Cosf) || ASineOrCosineArgument(expr, x) is not { } argument)
return null;
if (TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) && !TreeAnalyzer.IsZero(rate))
return OverAPowerOfALinear(expr, x, argument);
return OverAPowerOfALinear(expr, x, argument) ?? ByLoweringThePowerOfABinomialBesideX(expr, x, argument);
return UnderAPowerOfTheVariable(expr, x, argument);
}

/// <summary>
/// <c>T/(x (a + b x^n)^k)</c>, for T a polynomial in the sine and cosine of a linear, n three or
/// four and k two or more, as <c>T/(a x (a + b x^n)^(k - 1)) - (b/a) x^(n - 1) T/(a + b x^n)^k</c>,
/// since <c>a + b x^n - b x^n</c> is a: Rubi's 4.1.11 <c>sin(c + d x)/(x (a + b x^3)^3)</c> was
/// declined, the split over x and the binomial's three roots, each to the cube, past the size it
/// hands on, where each of the two is a question answered at once. Null where either is not.
/// </summary>
private static Entity? ByLoweringThePowerOfABinomialBesideX(Entity expr, Entity.Variable x, Entity argument)
{
if (!TryReadAsQuotient(expr, out var numerator, out var denominator))
return null;
// x in the numerator only in the sine and cosine of the argument.
var (sine, cosine) = (MathS.Sin(argument), MathS.Cos(argument));
if (numerator.Replace(node => node == sine || node == cosine ? Number.Integer.One : node).ContainsNode(x))
return null;
Entity constant = Number.Integer.One;
Entity? binomial = null;
var power = 0;
var xOnce = false;
foreach (var factor in Mulf.LinearChildren(denominator))
{
if (!factor.ContainsNode(x))
constant *= factor;
else if (factor == x && !xOnce)
xOnce = true;
else if (binomial is null && factor is Powf(var @base, Number.Integer { EInteger: var k }) && k.CompareTo(EInteger.FromInt32(2)) >= 0
&& k.CanFitInInt32() && TheRootsOfABinomial(@base, x) is not null)
(binomial, power) = (@base, k.ToInt32Unchecked());
else
return null;
}
if (!xOnce || binomial is null || !TreeAnalyzer.TryGetPolynomial(binomial, x, out var terms))
return null;
var free = terms[EInteger.Zero];
var top = terms.First(term => !term.Key.IsZero);
var lower = numerator / (constant * free * x * (power == 2 ? binomial : MathS.Pow(binomial, power - 1)));
var beside = -(top.Value / free) * MathS.Pow(x, Number.Integer.Create(top.Key - 1)) * numerator / (constant * MathS.Pow(binomial, power));
if (Integration.ComputeAsAQuestionOfItsOwn(lower, x, integrateByParts: true) is not { } first
|| Integration.ComputeAsAQuestionOfItsOwn(beside, x, integrateByParts: true) is not { } second)
return null;
return first + second;
}

// x - r, written x + |r| where the root is a negative number: x + 1 for the root -1.
private static Entity LessTheRoot(Entity root, Entity.Variable x) =>
root is Number.Complex { RealPart.IsNegative: true } or Number.Complex { RealPart.EDecimal.IsZero: true, ImaginaryPart.IsNegative: true }
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 System.Threading;
using System.Threading.Tasks;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A sine or cosine of a linear over x times a power of a binomial, with the power lowered by
/// <c>1/(x Q^k) = 1/(a x Q^(k - 1)) - (b/a) x^(n - 1)/Q^k</c> for <c>Q = a + b x^n</c>: Rubi's
/// 4.1.11 <c>sin(c + d x)/(x (a + b x^3)^3)</c> was declined, the split over x and the cube's three
/// roots, each to the third power, past the size it hands on, where each of the two is answered.
/// <a href="https://github.com/asc-community/AngouriMath/issues/1501">#1501</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfABinomialBesideXBelowASineIntegralTest
{
[Theory]
// Rubi 4.1.11:136.
[InlineData("sin(c + d*x)/(x*(a + b*x^3)^3)")]
[InlineData("cos(c + d*x)/(x*(a + b*x^3)^3)")]
public void IsAnsweredWithinTheGuard(string integrand)
{
Entity? integral = null;
// A thread of its own, so that the guard is not spent waiting behind other tests' work.
var task = Task.Factory.StartNew(() => integral = integrand.ToEntity().Integrate("x"),
CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}");
var text = integral!.Stringize();
Assert.DoesNotContain("integral(", text);
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.2, -0.9, 0.3, 1.4, 2.1 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
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}");
}
}
}
}
Loading