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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,21 @@ those now. Each answer holds on both sides of zero, an odd root of a negative be
| `"x^4*(a + b*x^3)^(1/3)/(c + d*x^3)".ToEntity().Integrate("x")` | `integral(...)` | the same, beside a rational function of it |
| `"x/((1 + x^3)^(2/3)*(2 + x^3))".ToEntity().Integrate("x")` | `integral(...)` | the same with numbers |

### A rational function of the tangent of a linear argument with symbols in it is not simplified under the linear substitution

**Answers where there were none.** The substitution search reaches `tan(g + h x)` through `u = g + h x`,
and simplified the quotient of the integrand by `h` in `u` before asking for its integral: with the
nine symbols of Rubi's 4.3.4.2 in it that took about half a minute, for an expression that is the
integrand with its argument renamed and holds nothing to simplify. Under a linear candidate, where
the integrand in `u` is a function of the tangent alone and holds no imaginary unit, the quotient
is handed on as it is
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a + b*tan(g + h*x))*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(c + d*tan(g + h*x))^2".ToEntity().Integrate("x")` | `integral(...)` after 33 s | the antiderivative, in 4.5 s |
| `"(c + d*tan(g + h*x))^3*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(a + b*tan(g + h*x))^3".ToEntity().Integrate("x")` | no answer within a minute and a half | the antiderivative, in 18 s |

### A rational function of `x^n` beside a power of a symbolic binomial is integrated without a search

**Answers where there were none.** A rational function of `x^n` beside `(c + d x^n)^(k - 1/n)` is a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23649,6 +23649,17 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity.
return false;
}

/// <summary>
/// Whether every function of <paramref name="u"/> in <paramref name="expr"/> is its tangent
/// or its cotangent: sums, products, quotients and powers of those, and of anything free of
/// <paramref name="u"/>.
/// </summary>
private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
=> expr.Nodes.All(node => node is Sumf or Minusf or Mulf or Divf or Powf or Number or Variable
|| !node.ContainsNode(u)
|| node is Tanf(var argument) && argument == u
|| node is Cotanf(var cotangentArgument) && cotangentArgument == u);

internal static Entity? SolveBySubstitution(Entity expr, Entity.Variable x, bool integrateByParts = true)
{
// A rational function over written linear factors with symbols in their
Expand Down Expand Up @@ -23800,7 +23811,20 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity.
&& expr.Complexity <= (expr.Vars.Any(v => v != x) ? LargestSymbolicIntegrandCollected : LargestIntegrandOfferedSums)
? WithThePowersOfXCollected(Functions.SingleQuotient.Combine(expr / duDx), x)
: source / duDx;
integrandInU = SimplifiedWithoutTheImaginaryUnit(InTermsOf(quotient, u, uSub, x), expr);
// Under a linear candidate the quotient is the integrand with its argument
// renamed, over a constant, and where it is a function of the tangent alone
// there is nothing in it for the simplifier to find:
// `(a + b tan(e + f x))(A + B tan(e + f x) + C tan(e + f x)^2)/(c + d tan(e + f x))^2`
// under `u = e + f x` spent 28 of its 32 s being simplified, nine symbols and
// nothing to cancel. Only there: beside a sine, a secant, a logarithm or the
// imaginary unit the simplified form is what the rules after this read, and
// `cot(c + d x)^8/(a + a sin(c + d x))` went from one second to seventy without it.
var inTermsOfU = InTermsOf(quotient, u, uSub, x);
integrandInU = !inTermsOfU.ContainsNode(x)
&& TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out var offset) && !slope.ContainsNode(x) && !offset.ContainsNode(x)
&& IsAFunctionOfTheTangentAlone(inTermsOfU, uSub) && !HoldsTheImaginaryUnit(inTermsOfU)
? Functions.PartialFractions.Bare(inTermsOfU.InnerSimplified)
: SimplifiedWithoutTheImaginaryUnit(inTermsOfU, expr);
// A factor written on both sides of the bar cancelled, where x survived:
// the one-level simplification leaves `u/((a w + b)^2 p u)` as it is, and
// the candidate was refused for the u it did not cancel.
Expand Down
54 changes: 54 additions & 0 deletions Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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 rational function of the tangent of a linear argument with symbols for its
/// coefficients, Rubi's 4.3.3.1 and 4.3.4.2. The substitution search reaches the tangent
/// through <c>u = g + h x</c>, and under a linear candidate the quotient by its derivative is
/// the integrand with its argument renamed over a constant: there is nothing in it to
/// simplify, and simplifying it with nine symbols in it was most of the time each of these
/// took.
/// <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, at
/// points where the tangent and every factor below the bar stay away from their poles and
/// zeros.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class LinearArgumentSubstitutionTest
{
private static readonly double[] Points = { 0.2, 0.5, 0.8 };

[Theory]
[InlineData("(a + b*tan(g + h*x))*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(c + d*tan(g + h*x))^2")]
[InlineData("(a + b*tan(g + h*x))*(A + B*tan(g + h*x))/(c + d*tan(g + h*x))^2")]
[InlineData("(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/((a + b*tan(g + h*x))*(c + d*tan(g + h*x)))")]
public void ARationalFunctionOfTheTangentOfALinear(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.9)
.Substitute("g", 0.3).Substitute("h", 1.1).Substitute("A", 0.5).Substitute("B", 1.7).Substitute("K", 0.4);
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}");
}
}
}
}
Loading