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 @@ -740,6 +740,19 @@ inner-simplified now, and declined in seconds where the parts do not separate
| `"x^4*e^(2*i*atan(a + b*x))".ToEntity().Integrate("x")` | `integral(...)` | a polynomial, logarithms and arctangents of `a + b x` |
| `"sec(c + d*x)^2/(a + i*a*tan(c + d*x))".ToEntity().Integrate("x")` | `ln(i a d tan(c + d x) + a d)/(i a d)` | `ln(i tan(c + d x) + 1)/(i a d)`, the same up to a constant |

### A root of a square in a trigonometric function is its modulus

**Answers where there were none.** `(a + b sin(x)) sqrt(b^2 + 2 a b sin(x) + a^2 sin(x)^2)` was
declined or past the budget, with the rest of Rubi's 4.7.7 half-odd powers of a perfect square in a
sine, tangent or secant. The radicand is `a^2 (f + b/a)^2`, so its root is `sqrt(a^2) |f + b/a|`,
written with the sign of `f + b/a` in front of the integral, as a root of a perfect square in `x`
already was ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a + b*sin(x))*sqrt(b^2 + 2*a*b*sin(x) + a^2*sin(x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(sin(x) + b/a) sqrt(a^2)` times an expression in `x`, `cos(x)` and `sin(2x)`, `provided a^2 > 0` |
| `"(a + b*tan(x))/sqrt(b^2 + 2*a*b*tan(x) + a^2*tan(x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(tan(x) + b/a) a/sqrt(a^2)` times the antiderivative of `(a + b tan(x))/(a tan(x) + b)`, `provided a^2 > 0` |

### `NaN` was returned as the antiderivative of something that has one

**A wrong answer, not a missing one.** `1/(a*x^2)` came back as `NaN + C`, and `NaN` is this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18029,6 +18029,86 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
return assumed == Entity.Boolean.True ? answer : answer.Provided(assumed);
}

/// <summary>
/// A half-odd power of a perfect square in one trigonometric function of <paramref name="x"/>
/// is that power of the modulus: <c>sqrt(b^2 + 2 a b sin(y) + a^2 sin(y)^2)</c> is
/// <c>sqrt(a^2) |sin(y) + b/a|</c>, which is <c>sqrt(a^2) sgn(sin(y) + b/a) (sin(y) + b/a)</c>,
/// the sign constant between the zeros of the linear in the sine.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="SolveByTakingARootOfAPerfectSquare"/>'s reading, with the function for the
/// variable: Rubi's 4.7.7 has <c>(a + b sin(d + e x)) sqrt(b^2 + 2 a b sin(d + e x) + a^2 sin(d + e x)^2)</c>
/// and the same with a tangent or a secant, at the half-odd powers either way, and they
/// were declined or past the budget where with the modulus written the rest is rational in
/// the function. At the top only, and only where each such root is a factor of the
/// integrand, so that its sign goes in front; a leading coefficient a number or one of
/// known sign for a real parameter, whose condition travels with the answer, as that
/// rule's does.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveByTakingARootOfAPerfectSquareInATrigonometricFunction(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
var factors = FactorsOfTheIntegrand(expr).Select(pair => pair.Factor).ToList();
var changed = false;
var declined = false;
Entity assumed = Entity.Boolean.True;
Entity signs = Number.Integer.One;
var written = expr.Replace(node =>
{
if (declined || node is not Powf(var radicand, Number.Rational r) || r is Number.Integer
|| !r.ERational.Denominator.Equals(EInteger.FromInt32(2)) || !radicand.ContainsNode(x)
|| radicand.Complexity > 40 || TreeAnalyzer.TryGetPolynomial(radicand, x, out _))
return node;
var functions = radicand.Nodes.Where(inner => inner.ContainsNode(x)
&& inner is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf).Distinct().ToList();
if (functions.Count != 1)
return node;
var function = functions[0];
var w = Variable.CreateUnique(expr, "w_sq");
var inW = radicand.Replace(inner => inner == function ? w : inner);
if (inW.ContainsNode(x) || !TreeAnalyzer.TryGetPolynomial(inW, w, out var monomials)
|| monomials.Keys.Max() is not { } degree || !degree.Equals(EInteger.FromInt32(2))
|| monomials.Keys.Any(k => k.Sign < 0))
return node;
var a = monomials[degree];
var b = monomials.TryGetValue(EInteger.One, out var b1) ? b1 : Number.Integer.Zero;
var c = monomials.TryGetValue(EInteger.Zero, out var c0) ? c0 : Number.Integer.Zero;
if (b.Evaled is Number.Complex and not Number.Real || c.Evaled is Number.Complex and not Number.Real
|| !IsAPerfectSquareDiscriminant(a, b, c))
return node;
if (a.Evaled is not Number.Real { IsPositive: true })
{
if (!IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed))
return node;
assumed = leadingAssumed;
}
// The sign in front of the integral, which needs the root to be a factor of it.
if (factors.Count(factor => factor == node) < expr.Nodes.Count(inner => inner == node))
{
declined = true;
return node;
}
var h = (b / (Number.Integer.Create(2) * a)).InnerSimplified;
if (h.Vars.Any())
h = Functions.PartialFractions.Bare(h.Simplify());
var linear = h == Number.Integer.Zero ? function : (function + h).InnerSimplified;
var twoR = Number.Integer.Create(r.ERational.Numerator);
signs = signs * MathS.Signum(linear);
changed = true;
return MathS.Pow(a, r) * (twoR == Number.Integer.One ? linear : MathS.Pow(linear, twoR));
});
if (declined || !changed)
return null;
if (Integration.ComputeAsTheSameQuestion(written, x, integrateByParts) is not { } answer)
return null;
answer = signs * answer;
return assumed == Entity.Boolean.True ? answer : answer.Provided(assumed);
}

/// <summary>
/// A fractional power of a sum whose every term has x to a power in it,
/// <c>(a x^j + b x^n)^p</c> with <c>0 &lt; j &lt; n</c>, as <c>K x^(j p) (a + b x^(n - j))^p</c>:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveAReciprocalOfAnInverseTrigonometricFunction(expr, x, integrateByParts)) is { }) return answer;
// A fractional power of a perfect square is the power of the modulus, sgn(P) P^(2r).
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquare(expr, x, integrateByParts)) is { }) return answer;
// And a square in one trigonometric function, at the top: the modulus of the linear in it.
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquareInATrigonometricFunction(expr, x, integrateByParts)) is { }) return answer;
// And any power of a square in any power of x, as the power of its root times a factor
// constant where the root is not zero: `x^2 (a^2 + 2 a b x^3 + b^2 x^6)^p`.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfASquareAsAPowerOfItsRoot(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// 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 half-odd power of <c>b^2 + 2 a b f + a^2 f^2</c>, a perfect square in one trigonometric
/// function <c>f</c>, is that power of <c>sqrt(a^2) |f + b/a|</c>, the sign in front of the
/// integral. Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back with <c>a = 1.3</c>, <c>b = 0.7</c>, on both sides of the
/// zeros of <c>f + b/a</c> and of zero.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class RootOfASquareInATrigonometricFunctionIntegralTest
{
[Theory]
[InlineData("(a + b*sin(x))*sqrt(b^2 + 2*a*b*sin(x) + a^2*sin(x)^2)")]
[InlineData("(a + b*tan(x))/sqrt(b^2 + 2*a*b*tan(x) + a^2*tan(x)^2)")]
[InlineData("(a + b*sec(x))*(b^2 + 2*a*b*sec(x) + a^2*sec(x)^2)^(3/2)")]
[InlineData("(a + b*sin(g + f*x))/(b^2 + 2*a*b*sin(g + f*x) + a^2*sin(g + f*x)^2)^(3/2)")]
public void IsAPowerOfTheModulusOfTheLinear(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("g", 0.4).Substitute("f", 1.1);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.3, -1.1, -0.3, 0.4, 1.1, 2.3 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.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) + Math.Abs((double)want.ImaginaryPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}
}
}
Loading