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
20 changes: 20 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,26 @@ partial fractions divide. It is taken out in front of the integral first
| `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 76,249 characters, the length the content-free one already had |
| `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters |

### A whole power is taken out of a power of a product, in front of a constant

**Answers where there were none.** `(c sin(a + b x)^3)^(1/3)/x` was declined: nothing read a function under a root of
its own power. `(c R f^n)^r` is `K f^(n r) R^r` with `K = (c R f^n)^r/(f^(n r) R^r)`, constant wherever it is defined,
and only a factor of the integrand is rewritten. A constant stays under the root inside `K`, so a negative `c` is
answered too: an odd root is the real root
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(c*sin(a + b*x)^3)^(1/3)".ToEntity().Integrate("x")` | `integral(...)` | 35 characters |
| `"(c*sin(a + b*x)^3)^(2/3)".ToEntity().Integrate("x")` | `integral(...)` | 78 characters |
| `"(c*sin(a + b*x)^3)^(1/3)/x".ToEntity().Integrate("x")` | `integral(...)` | 105 characters |
| `"(c*sin(a + b*x)^3)^(2/3)/x^3".ToEntity().Integrate("x")` | `integral(...)` | 391 characters |
| `"(c*sin(a + b*x^n)^3)^(1/3)/x".ToEntity().Integrate("x")` | `integral(...)` | 129 characters |
| `"sqrt((x^2 - 1)^2/(x*(1 + x^2)))/(1 + x^2)".ToEntity().Integrate("x")` | `integral(...)` | 167 characters |
| `"1/sqrt((a^2 + x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 104 characters |
| `"1/sqrt((a + b*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 100 characters |
| `"1/sqrt(1 - coth(x)^2)".ToEntity().Integrate("x")` | `integral(...)` | 301 characters |

### A power of x past the twelfth beside a block is split off

**Answers where there were none.** `(c + d x^3 + g x^6 + f x^9)/(x^14 (a + b x^3))` was declined, and so were
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19031,6 +19031,95 @@ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out
}
}

/// <summary>
/// A power of a product with a factor under a whole power, that power taken out:
/// <c>(c R f^n)^r</c> is <c>K f^(n r) R^r</c>, where <c>K = (c R f^n)^r/(f^(n r) R^r)</c> and
/// <c>c</c> is free of <c>x</c>. The logarithmic derivatives of both sides are
/// <c>r (R'/R + n f'/f)</c>, so <c>K</c> is
/// constant wherever it is defined, and it stands in front of the answer the way Rubi
/// writes <c>(c sin(a + b x)^3)^(1/3) csc(a + b x)</c> in front of its.
/// </summary>
/// <remarks>
/// <c>(c sin(a + b x)^3)^(1/3)/x</c>, <c>sqrt((x^2 - 1)^2/(x (1 + x^2)))/(1 + x^2)</c> and
/// <c>1/sqrt((a^2 + x^2)^3)</c> were declined: nothing reads a function under a root of its
/// own power. With the power taken out each is <c>K</c> times an integrand the chain
/// answers. After every other rule, and at the question asked only.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByTakingTheWholePowersOutOfAPowerOfAProduct(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked || takingTheWholePowersOut)
return null;
var multiple = Variable.CreateUnique(expr, "k_out");
Entity? constant = null;
var rewritten = AFactor(expr);
if (constant is null)
return null;
var simplified = rewritten.InnerSimplified;
Entity? answer;
takingTheWholePowersOut = true;
try
{
answer = Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts);
}
finally
{
takingTheWholePowersOut = false;
}
if (answer is null)
return null;
answer = answer.Substitute(multiple, constant);
return answer.ContainsNode(multiple) ? null : answer;

// Only a factor of the integrand: inside a function, `ln(1/(c x) + sqrt(1/(c^2 x^2) - 1))`
// for `asech(c x)`, K is not a factor of anything, and the question it writes is no shorter.
Entity AFactor(Entity node) => node switch
{
Mulf(var left, var right) => AFactor(left) * AFactor(right),
Divf(var above, var below) => AFactor(above) / AFactor(below),
_ => TakeOut(node),
};

Entity TakeOut(Entity node)
{
if (constant is not null || node is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer
|| !@base.ContainsNode(x))
return node;
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(@base));
Entity outside = Number.Integer.One;
Entity inside = Number.Integer.One;
var taken = false;
foreach (var (side, sign) in new[] { (above, 1), (below, -1) })
foreach (var factor in Mulf.LinearChildren(side))
{
var (factorBase, factorExponent) = factor switch
{
Powf(var b, Number.Integer n) => (b, n),
_ => (factor, Number.Integer.One),
};
if (!factorBase.ContainsNode(x))
continue;
if (factorExponent.EInteger.Abs().CompareTo(EInteger.FromInt32(2)) >= 0)
{
outside *= MathS.Pow(factorBase, (sign * factorExponent * exponent).InnerSimplified);
taken = true;
}
else
inside = sign > 0 ? inside * factor : inside / factor;
}
if (!taken)
return node;
// A factor free of x stays under the power, in K: taken out, `c^(1/3)` is only
// `c`'s cube root for `c > 0`.
var written = inside == Number.Integer.One ? outside : outside * MathS.Pow(inside, exponent);
constant = node / written;
return multiple * written;
}
}

/// <summary>Set while <see cref="SolveByTakingTheWholePowersOutOfAPowerOfAProduct"/> asks its question.</summary>
[System.ThreadStatic] private static bool takingTheWholePowersOut;

/// <summary>
/// A rational function of <c>x</c> and one square root of a quadratic in <c>x</c>,
/// rationalised by an Euler substitution and handed to the rational integrator.
Expand Down Expand Up @@ -28451,7 +28540,11 @@ Entity Rewritten(Entity side, Number.Integer sign)
if (constantAlone && (constant == Number.Integer.One || restFactors > 1
|| !EntersOnlyThroughTrigonometricFunctions(single!, x)))
return node;
if (constant != Number.Integer.One && constant.Evaled is not Number.Real { IsPositive: true })
// An odd denominator is the real root, `(-8)^(1/3) = -2`, which splits across a
// product for any real constant: `(c sin(x)^3)^(1/3)` is `c^(1/3) sin(x)` for a
// negative `c` as well, and asking `c > 0` left it undefined there.
var aRealRoot = exponent is Number.Rational { ERational: var fraction } && !fraction.Denominator.IsEven;
if (constant != Number.Integer.One && constant.Evaled is not Number.Real { IsPositive: true } && !aRealRoot)
{
if (constant.Vars.Any() is false)
return node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// And a sum with the root below the bar, over its conjugate: after every rule above,
// which answer the numeric spellings, `1/(a c + b c x^2 + d sqrt(a + b x^2))` among them.
if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(expr, x, integrateByParts)) is { }) return answer;
// And a power of a product with a factor under a whole power, that power taken out in
// front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer;
// Product-to-sum among the rewrites rather than before them, because a product of
// trigonometric functions of *equal* arguments is a power and wants a different tool;
// this only fires where the arguments differ, which is exactly what every substitution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// 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 power of a product with a factor under a whole power, that power taken out in front of
/// a constant: <c>(c sin(a + b x)^3)^(1/3)</c> is <c>K sin(a + b x)</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Rubi's 4.1.12 and 1.3.2 were declined. Each answer is differentiated back with the constant
/// under the root on both signs: an odd root is the real root, and a negative <c>c</c> used to
/// be answered <c>provided c &gt; 0</c>.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class AWholePowerTakenOutOfAPowerOfAProductIntegralTest
{
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());

var derivative = integral.Substitute("C", 0).Differentiate("x");
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
derivative = derivative.Substitute(name, value);
original = original.Substitute(name, value);
}

var compared = 0;
foreach (var at in points)
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
if (got.IsNaN || want.IsNaN)
continue;
compared++;
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));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, "
+ $"where the integrand is {want}");
}
Assert.True(compared >= 3,
$"only {compared} of {points.Length} points were comparable for {integrand}");
}

[Theory]
[InlineData("(c*sin(a+b*x)^3)^(1/3)")]
[InlineData("(c*sin(a+b*x)^3)^(2/3)")]
[InlineData("(c*sin(a+b*x)^3)^(1/3)/x")]
[InlineData("(c*sin(a+b*x)^3)^(2/3)/x^3")]
[InlineData("(c*sin(a+b*x^2)^3)^(1/3)/x^3")]
public void AConstantOfEitherSignUnderAnOddRoot(string integrand)
{
var points = new[] { -1.4, -0.3, 0.8, 2.25 };
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", 2));
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", -3));
}

[Theory]
[InlineData("sqrt((-1+x^2)^2/(x*(1+x^2)))/(1+x^2)", new[] { 0.5, 0.8, 2.0, 3.0 })]
[InlineData("1/sqrt((a^2+x^2)^3)", new[] { -2.0, 0.3, 2.5 })]
[InlineData("1/sqrt((a+b*x^2)^3)", new[] { -1.3, 0.3, 1.1 })]
public void ARootOfAPowerOfAPolynomial(string integrand, double[] points)
=> DifferentiatesBack(integrand, points, ("a", 2), ("b", -1));
}
}
Loading