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
22 changes: 22 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,28 @@ either now; and the secant quotient was simplified to `cos/(b^(5/2) d)` through
`(1/cos)^(3/2) = cos^(-3/2)`, which is wrong for every negative cosine and cancelled against the
same wrong step on the denominator. On the 1774-problem independent suites this is 1707 to 1705, the two `asin` rows, with the sign error gone.

### A function comes out of a fractional power of its even power with its sign

**Improvement, not silent.** The entry two above made `(sin(x)^2)^(3/2)` the modulus `|sin(x)|^3`
and left three of Rubi's integrands unevaluated for it, and answered two more in powers of the
modulus through the substitutions. The modulus is the function times its sign, and the sign is a
constant between the function's zeros: `(a sin(x)^2)^(5/2)` is `a^(5/2) sgn(sin(x)) sin(x)^5`, the
sign comes out in front of the integral, and what is left is a whole power any rule reads. An even
power of a real function is not negative, so `(c q)^p = c^p q^p` holds for the principal powers
whatever `c` is, and nothing is assumed about the constant — checked at `a = -3` as well as
`a = 2`. Whole products of the exponents only, at the top of the integration only, and after the
rule that does the same for a polynomial under a square root, whose answers are as they were.
[#1387](https://github.com/asc-community/AngouriMath/issues/1387)

| | Was | Is |
|---|---|---|
| `"x*sqrt(sin(x)^2)".Integrate("x")` | unevaluated | `sgn(sin(x)) (sin(x) - x cos(x))` |
| `"(csc(x)^2)^(3/2)".Integrate("x")`, `"1/(csc(x)^2)^(7/2)".Integrate("x")` | unevaluated | `sgn(csc(x))` times the integral of `csc^3`, of `sin^7` |
| `"(sin(x)^2)^(3/2)".Integrate("x")` | `(2/3 tan(x/2)^5 + 2 tan(x/2)^3) ((2 tan(x/2))^2)^(1/2)/((tan(x/2)^2 + 1)^3)` | `sgn(sin(x)) (cos(x)^3/3 - cos(x))` |
| `"(a*sin(x)^2)^(5/2)".Integrate("x")` | `sgn(sin(x)) a^(5/2) (...)` in `(1 - abs(sin(x))^2)^(1/2)` over `sgn(cos(x))` | `sgn(sin(x)) a^(5/2) (2 cos(x)^3/3 - cos(x) - cos(x)^5/5)` |
| `"1/sqrt(a*cot(x)^2)".Integrate("x")` | `-sgn(tan(x)) ln(1/sqrt(1 + abs(tan(x))^2))/sqrt(a)` | `sgn(cot(x)) ln(1 + tan(x)^2)/(2 sqrt(a))` |
| `"(b*tan(x)^2)^(5/2)".Integrate("x")` | `sgn(tan(x)) b^(5/2) ((abs(tan(x))^2)^2/2 - abs(tan(x))^2 + ln(abs(tan(x))^2 + 1))/2` | the same with `tan(x)` for `abs(tan(x))` |

### A condition the answer states on its own is no longer repeated beside it

**Improvement, not silent.** `Simplify` returned `(1 - ln(x))/x^2 provided not x = 0` for the
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Calculus/CombinedRadicalsTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Calculus/EvenPowerSignTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Calculus/RothsteinTragerTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11371,6 +11371,82 @@ bool IsAConstantTimesAPowerOfOneMinusUSquared(Entity polynomial, out Entity cons
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A fractional power of a constant times an even power of a function of x, the
/// function taken out of the power with its sign: <c>(a sin(x)^2)^(5/2)</c> is
/// <c>a^(5/2) |sin(x)|^5</c>, which is <c>a^(5/2) sgn(sin(x)) sin(x)^5</c> -- the sign
/// a constant between the sine's zeros, carried through the integration as a symbol
/// and written back. Rubi's <c>x sqrt(sin(x)^2)</c> is <c>sgn(sin(x)) (sin(x) - x cos(x))</c>
/// that way, and <c>1/(csc(x)^2)^(7/2)</c> is <c>sgn(csc(x))</c> times the seventh power
/// of the sine, where the substitutions read the root of the square as a modulus and
/// answered in powers of it.
/// </summary>
/// <remarks>
/// An even power of a real function is not negative, so <c>(c q)^p = c^p q^p</c> holds
/// for the principal powers whatever <c>c</c> is, and <c>(f^(2k))^p</c> is <c>|f|^(2kp)</c>.
/// Whole products of the exponents only, so that what is handed on is a whole power of
/// the function; a polynomial radicand is the rule above's, which comes first. At the
/// top only, as there: a substitution's variable is one it knows the sign of, and a
/// sign written for it is one nothing differentiates.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByTakingAFunctionOutOfAPowerOfItsEvenPower(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
Entity signs = Number.Integer.One;
var rewritten = expr.Replace(node =>
{
if (node is not Powf(var radicand, Number.Rational exponent) || exponent is Number.Integer || !radicand.ContainsNode(x))
return node;
// The radicand read as a constant times even whole powers of functions of x,
// each power whole once multiplied by the exponent; anything else is not this
// rule's.
Entity constant = Number.Integer.One;
var functions = new List<(Entity function, EInteger power)>();
var top = radicand;
if (radicand is Divf(var above, var below) && !below.ContainsNode(x))
{
constant = Number.Integer.One / below;
top = above;
}
foreach (var factor in Mulf.LinearChildren(top))
{
if (!factor.ContainsNode(x))
{
constant = constant * factor;
continue;
}
// A real function: a fractional power of one is not, where its base is
// negative, and its even power is then not the square of a modulus.
if (factor is not Powf(var function, Number.Integer degree) || !degree.EInteger.IsEven || degree.EInteger.Sign <= 0
|| TreeAnalyzer.TryGetPolynomial(function, x, out _) || function is Powf(_, not Number.Integer))
return node;
var product = exponent.ERational.Multiply(ERational.FromEInteger(degree.EInteger)).ToLowestTerms();
if (!product.Denominator.Equals(EInteger.One))
return node;
functions.Add((function, product.Numerator));
}
if (functions.Count == 0)
return node;
Entity outside = constant == Number.Integer.One ? Number.Integer.One : MathS.Pow(constant, exponent);
foreach (var (function, power) in functions)
{
// |f|^n is f^n for an even n and sgn(f) f^n for an odd one.
if (!power.IsEven)
signs = signs * MathS.Signum(function);
outside = outside * MathS.Pow(function, Number.Integer.Create(power));
}
return outside;
});
if (rewritten == expr)
return null;
if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } result)
return null;
var answer = signs == Number.Integer.One ? result : signs * result;
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A denominator factor that is a sum or difference of two square roots of
/// polynomials, multiplied above and below by its conjugate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// A square root of a polynomial with a repeated factor, the factor taken out of
// the root with its sign: `sqrt((x - 3)^2 (x + 1))` is `sgn(x - 3) (x - 3) sqrt(x + 1)`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingASquareFactorOutOfARoot(expr, x, integrateByParts)) is { }) return answer;
// A fractional power of a constant times an even power of a function, the function
// taken out with its sign: `(a sin(x)^2)^(5/2)` is `a^(5/2) sgn(sin(x)) sin(x)^5`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingAFunctionOutOfAPowerOfItsEvenPower(expr, x, integrateByParts)) is { }) return answer;
// A sum of two roots below the bar, multiplied above and below by its conjugate:
// `1/(sqrt(A) - sqrt(B))` is `(sqrt(A) + sqrt(B))/(A - B)`.
if ((answer = IndefiniteIntegralSolver.SolveByRationalisingASumOfRoots(expr, x, integrateByParts)) is { }) return answer;
Expand Down
83 changes: 83 additions & 0 deletions Sources/Tests/UnitTests/Calculus/EvenPowerSignTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// 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 fractional power of a constant times an even power of a function, the function
/// taken out of the power with its sign: <c>(a sin(x)^2)^(5/2)</c> is
/// <c>a^(5/2) sgn(sin(x)) sin(x)^5</c>, and the sign is a constant between the zeros of
/// the sine that comes out in front of the integral.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Rubi's <c>x sqrt(sin(x)^2)</c>, <c>(csc(x)^2)^(3/2)</c> and <c>1/(csc(x)^2)^(7/2)</c>
/// had no antiderivative, and <c>(a sin(x)^2)^(5/2)</c> one in powers of the modulus of the
/// sine. Every answer is differentiated back on both sides of a zero of the function, and
/// the constant is given a negative value as well as a positive one: an even power is not
/// negative, so <c>(c q)^p = c^p q^p</c> for the principal powers whatever <c>c</c> is.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class EvenPowerSignTest
{
/// <summary>On both sides of the sine's zero at pi, and of the cosine's at pi/2 and 3pi/2.</summary>
private static readonly double[] AroundTheZeros = { 0.5, 1.2, 2.0, 3.5, 5.0, 6.5 };

private static void DifferentiatesBack(string integrand, double[] points, double constant)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Assert.DoesNotContain("abs(", integral.Stringize());

var derivative = integral.Substitute("C", 0).Substitute("a", constant).Differentiate("x");
var original = integrand.ToEntity().Substitute("a", constant);
foreach (var at in points)
{
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} at a = {constant} is {got} at x = {at}, "
+ $"where the integrand is {want}");
}
}

[Theory]
[InlineData("(a*sin(x)^2)^(5/2)")]
[InlineData("(a*sin(x)^2)^(3/2)/a")]
[InlineData("x*sqrt(a*sin(x)^2)")]
[InlineData("(a*csc(x)^2)^(3/2)")]
[InlineData("1/(a*csc(x)^2)^(7/2)")]
[InlineData("1/sqrt(a*cot(x)^2)")]
[InlineData("(a*cos(x)^4)^(3/4)")]
public void TheFunctionComesOutOfThePowerWithItsSign(string integrand)
{
DifferentiatesBack(integrand, AroundTheZeros, 2);
DifferentiatesBack(integrand, AroundTheZeros, -3);
Assert.Contains("sgn(", integrand.ToEntity().Integrate("x").Stringize());
}

/// <summary>An even product of the exponents has no sign to keep.</summary>
[Theory]
[InlineData("(a*sin(x)^4)^(1/2)")]
[InlineData("1/(a*cos(x)^4)^(1/2)")]
[InlineData("(a*cos(x)^4)^(3/2)")]
public void AnEvenWholePowerNeedsNoSign(string integrand)
{
DifferentiatesBack(integrand, AroundTheZeros, 2);
DifferentiatesBack(integrand, AroundTheZeros, -3);
Assert.DoesNotContain("sgn(", integrand.ToEntity().Integrate("x").Stringize());
}
}
}
Loading