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
26 changes: 26 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3100,6 +3100,32 @@ answered. The exponent is simplified now. Rubi's 1.1.3.2
| `"1/(a + b*(c*x^n)^(2/n))".ToEntity().Integrate("x")` | `integral(...)` | an arctangent or a logarithm in `b c^(2/n) x`, by the sign of `a b c^(2/n)` |
| `"1/(1 + 4*(x^(2*n))^(1/n))".ToEntity().Integrate("x")` | `integral(...)` | `arctan(2 x)/2` |

### A symbolic power of a monomial is integrated on both sides of 0, for a constant of either sign

**Answers where there were none, and wrong ones on the unreleased master corrected.** 2.5.0 declined
`(c x^n)^b` for a symbolic `n`, and the unreleased master integrated it as `c^b x^(n b)`, `provided c > 0`, on
the premise that a symbolic `n` leaves the integrand real only for a positive `x`. An even `n` does not: `(x^m)^p`
was answered `x^(m p + 1)/(m p + 1)`, which at `m = 2`, `p = 1/2` is `x^2/2` where the integral of `|x|` is
`x |x|/2`, wrong for every `x < 0`; and through `u = ln(a x^n)`, `u = sin(x)` and `u = tan(x)` so were Rubi's 3.5
`(ln(a x^n)^m)^p/x` and `(b ln(a x^n)^m)^p/x` wherever the logarithm is negative, `(sin(x)^m)^p cos(x)` wherever
the sine is, and Rubi's 4.3 `(b tan(c + d x)^p)^(1/p)` wherever the tangent is. Where such a power is a factor of
the integrand it is written `K x^(n b)` with `K = (c x^n)^b/x^(n b)`, constant wherever the power is continuous
for every `c`, `n` and `b`, and `K` comes out in front of the integral: the answer for the power alone is
`x (c x^n)^b/(n b + 1)`, Rubi's, with no condition on `c`. Inside a sum the power is split as before. Of Rubi's
1.1.3.2, 51 answers on the unreleased master are 4 to 32 characters shorter, 47 of them for losing the
`provided b > 0`, and 12 are 3 to 96 characters longer: three that had nothing else to cancel `K` against, and
nine where the power stands in a sum beside itself as well, `(c x^n)^(1/n)/(a + b (c x^n)^(1/n))^2`
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(x^m)^p".ToEntity().Integrate("x")` | `integral(...)` | `x * (x ^ m) ^ p / (m * p + 1) + C`; `x ^ (m * p + 1) / (m * p + 1) + C` on the unreleased master |
| `"(c*x^n)^b".ToEntity().Integrate("x")` | `integral(...)` | `x * (c * x ^ n) ^ b / (b * n + 1) + C`; `(c ^ b * x ^ (b * n + 1) / (b * n + 1) provided c > 0) + C` on the unreleased master |
| `"(ln(a*x^n)^m)^p/x".ToEntity().Integrate("x")` | `integral(...)` | `ln(a * x ^ n) * (ln(a * x ^ n) ^ m) ^ p / (m * p + 1) / n + C`; `ln(a * x ^ n) ^ (m * p + 1) / (m * p + 1) / n + C` on the unreleased master |
| `"(sin(x)^m)^p*cos(x)".ToEntity().Integrate("x")` | `integral(...)` | `sin(x) * (sin(x) ^ m) ^ p / (m * p + 1) + C`; `sin(x) ^ (m * p + 1) / (m * p + 1) + C` on the unreleased master |
| `"1/(x*(b*x^n)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | `1 / ((-(1 + 3/2 * n) + 1) * (b * x ^ n) ^ (3/2)) + C`, 52 characters; 80, with `provided b > 0`, on the unreleased master |
| `"(b*tan(c+d*x)^p)^(1/p)".ToEntity().Integrate("x")` | `integral(...)` | 119 characters; 71 on the unreleased master |

### A function of a quotient of two linears is integrated over the quotient's denominator

**Answers where there were none.** `sin((a + b x)/(c + d x))` and its powers were declined, and so
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38176,7 +38176,8 @@ Entity Rewritten(Entity side, Number.Integer sign)

/// <summary>
/// A fractional or symbolic power of a monomial, distributed: <c>(c x^n)^b</c> is
/// <c>c^b x^(n b)</c> for a positive <c>c</c> and a <c>n</c> that is not whole.
/// <c>c^b x^(n b)</c> for a positive <c>c</c> and a fractional <c>n</c>, and
/// <c>K x^(n b)</c> for a symbolic one, with <c>K = (c x^n)^b/x^(n b)</c> constant.
/// </summary>
internal static Entity? SolveByDistributingAPowerOfAMonomial(Entity expr, Entity.Variable x, bool integrateByParts)
=> DistributeAFractionalPower(expr, x, integrateByParts, constantAlone: false, negativeArm: false);
Expand All @@ -38198,12 +38199,25 @@ Entity Rewritten(Entity side, Number.Integer sign)
/// folds to a power of <c>c x^n</c> and stopped there.
/// </summary>
/// <remarks>
/// Exact on the domain the integrand has. With a symbolic <c>n</c>, <c>x^n</c> is real
/// Exact on the domain the integrand has. With a fractional <c>n</c>, <c>x^n</c> is real
/// only for a positive <c>x</c> -- at a negative one it is a complex number or nothing --
/// so the integrand is defined there and nowhere else, and on <c>x &gt; 0</c> the
/// identity holds for a positive <c>c</c>. That condition travels with the answer as
/// <c>provided c &gt; 0</c>, unless <c>c</c> is a positive number already; the
/// <c>(e x)^(n - 1)</c> of Rubi's 6.5.2 carries the same one. A whole exponent is
/// and on <c>x &gt; 0</c> the identity holds for a positive <c>c</c>. That condition
/// travels with the answer as <c>provided c &gt; 0</c>, unless <c>c</c> is a positive
/// number already; the <c>(e x)^(n - 1)</c> of Rubi's 6.5.2 carries the same one.
/// A symbolic <c>n</c> confines nothing: it can be an even number, and then
/// <c>(x^n)^b</c> is real for a negative <c>x</c> too, where <c>x^(n b)</c> is not it --
/// <c>(x^2)^(1/2)</c> is <c>|x|</c> and <c>x^(2 (1/2))</c> is <c>x</c>, so Rubi's 3.5
/// <c>(ln(a x^n)^m)^p/x</c>, answered <c>ln(a x^n)^(m p + 1)/(n (m p + 1))</c>, had the
/// wrong sign wherever the logarithm is negative for <c>m = 2</c>, <c>p = 1/2</c>. There
/// the power is written <c>K x^(n b)</c> with <c>K = (c x^n)^b/x^(n b)</c>, whose logarithmic
/// derivative is <c>b n/x - n b/x = 0</c>, so it is constant wherever the power is
/// continuous, for every <c>c</c>, <c>n</c> and <c>b</c>: the answer is <c>K</c> times the
/// answer for <c>x^(n b)</c>, <c>x (x^n)^b/(n b + 1)</c> for the power alone, which is
/// Rubi's, and no condition on <c>c</c> is owed. Only for a factor of the integrand:
/// inside a sum <c>K</c> does not come out in front, and with it as a symbol there
/// <c>1/(1 + 4 (x^(2 n))^(1/n))</c> was a 568-character piecewise on the sign of
/// <c>K</c> where it is <c>arctan(2 x)/2</c>; there the power is split as for a
/// fractional <c>n</c>, which holds for <c>x &gt; 0</c>. A whole exponent is
/// <see cref="SolveByDistributingWholePowersOfProducts"/>'s, and needs no condition.
/// The two are asked from two places: the monomial where it always was, and the constant
/// alone after the rules that answer its shapes for any real constant, since this one
Expand All @@ -38222,6 +38236,23 @@ Entity Rewritten(Entity side, Number.Integer sign)
{
Entity assumed = Entity.Boolean.True;
var changed = false;
var named = new List<(Variable Name, Entity Value)>();
// The factors of the integrand, a whole power of one read as its base and a whole
// power of a product as its factors: a power among them takes its constant factor
// out in front of the integral.
var factorsOfTheIntegrand = new HashSet<Entity>();
void GatherTheFactors(Entity product)
{
foreach (var factor in Mulf.LinearChildren(product))
{
var raised = factor is Powf(var @base, Number.Integer) ? @base : factor;
if (raised is Mulf or Divf)
GatherTheFactors(raised);
else
factorsOfTheIntegrand.Add(raised);
}
}
GatherTheFactors(expr);
var symbolicConstantsTaken = 0;
var aSymbolicConstantIsNegated = false;
var written = expr.Replace(node =>
Expand Down Expand Up @@ -38288,8 +38319,9 @@ Entity Rewritten(Entity side, Number.Integer sign)
// not `x`'s -- `(2 u^3)^(3/2)` is `2^(3/2) sgn(u) u^(9/2)`, and splitting it
// without the sign is a wrong answer where `u < 0`, which the rules for a root
// of an even power answer correctly and this must leave to them. With a
// fractional or symbolic `n` the integrand is real only for a positive `x`, and
// there the split is exact. The constant comes out either way.
// fractional `n` the integrand is real only for a positive `x`, and there the
// split is exact; a symbolic one is split with its constant factor, below. The
// constant comes out either way.
// An even whole power of a function is not this rule's: `(a sin(x)^2)^(5/2)` is
// `a^(5/2) sgn(sin x) sin(x)^5` for *any* real `a`, because an even power is
// never negative, and the rule that takes the function out of it with its sign
Expand All @@ -38311,6 +38343,19 @@ Entity Rewritten(Entity side, Number.Integer sign)
// the rules that answer its shapes for any real constant.
if (split == constantAlone)
return node;
// A symbolic n can be whole, and even: `(x^n)^b` is then real for a negative x as
// well, and `x^(n b)` is not it -- `(x^2)^(1/2)` is `|x|`. The quotient
// K = (c x^n)^b / x^(n b) is constant wherever the power is continuous, for every
// c, n and b, so for a factor of the integrand it goes in front of the integral as
// a constant and is written back into the answer. See the remarks.
if (split && degree!.Evaled is not Number && factorsOfTheIntegrand.Contains(node))
{
var power = SimplifiedExponent((degree * exponent).InnerSimplified);
var name = Variable.CreateUnique(named.Aggregate(expr, (all, pair) => all + pair.Name), "k_monomial");
named.Add((name, node / MathS.Pow(x, power)));
changed = true;
return name * MathS.Pow(x, power);
}
// Taking the constant out rewrites the question, and the rewritten question is
// asked at the same depth, so the rewrite has to be one that brings the answer
// closer or it multiplies the search at every level. It does where the rest is a
Expand Down Expand Up @@ -38375,6 +38420,24 @@ Entity Rewritten(Entity side, Number.Integer sign)
return null;
if (Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts) is not { } answer)
return null;
// The last first: the replacement is made from the inside out, so a power around
// another holds the factor named for the inner one, `(a (b x^m)^n)^(1/(m n))`.
Entity WrittenBack(Entity written)
{
for (var i = named.Count - 1; i >= 0; i--)
written = written.Substitute(named[i].Name, named[i].Value);
return written;
}
answer = WrittenBack(answer);
// K's x^(-n b) beside the x^(n b + 1) of the power rule is x: the answer for the
// power alone is x (c x^n)^b/(n b + 1).
if (named.Count > 0)
{
var powers = named.Select(pair => WrittenBack(((Divf)pair.Value).Dividend)).ToList();
answer = answer.Replace(node => node is Mulf or Divf && Flattened(node).Any(factor => powers.Contains(factor)
|| factor is Powf(var below, var minusOne) && minusOne == Number.Integer.MinusOne && powers.Contains(below))
? WithThePowersOfXGathered(node, x) : node);
}
if (assumed == Entity.Boolean.True)
return answer;
var thisArm = answer.Provided(assumed);
Expand All @@ -38388,6 +38451,67 @@ Entity Rewritten(Entity side, Number.Integer sign)

static Entity SimplifiedExponent(Entity exponent)
=> exponent is Number || exponent.Complexity > 40 ? exponent : Functions.PartialFractions.Bare(exponent.Simplify());

// The powers of x among the factors of a product as one power, the exponent
// simplified: `x^a x^b` is `x^(a + b)` for every a and b on the principal branch,
// x = 0 aside.
static Entity WithThePowersOfXGathered(Entity product, Entity.Variable x)
{
Entity exponent = Number.Integer.Zero;
var gathered = 0;
var above = new List<Entity>();
var below = new List<Entity>();
foreach (var factor in Flattened(product))
switch (factor)
{
case Entity.Variable bare when bare == x:
exponent += Number.Integer.One;
gathered++;
break;
case Powf(Entity.Variable bare, var power) when bare == x && !power.ContainsNode(x):
exponent += power;
gathered++;
break;
case Powf(Powf(Entity.Variable bare, var power), var minusOne) when bare == x && !power.ContainsNode(x)
&& minusOne == Number.Integer.MinusOne:
exponent -= power;
gathered++;
break;
case Powf(var divisor, var minusOne) when minusOne == Number.Integer.MinusOne:
below.Add(divisor);
break;
default:
above.Add(factor);
break;
}
if (gathered < 2)
return product;
var folded = SimplifiedExponent(exponent.InnerSimplified);
if (folded != Number.Integer.Zero)
above.Insert(0, folded == Number.Integer.One ? x : MathS.Pow(x, folded));
var numerator = above.Count == 0 ? Number.Integer.One : above.Aggregate((all, factor) => all * factor);
return below.Count == 0 ? numerator : numerator / below.Aggregate((all, factor) => all * factor);
}

// The factors of a product, with a quotient below the bar read as its factors there:
// `1/(K/x^m)` is `x^m K^(-1)`, where the factor in front stands below the bar. A
// divisor below a divisor arrives inverted twice.
static IEnumerable<Entity> Flattened(Entity product)
{
static Entity Inverted(Entity part)
=> part is Powf(var divisor, var minusOne) && minusOne == Number.Integer.MinusOne ? divisor : MathS.Pow(part, Number.Integer.MinusOne);
foreach (var factor in Mulf.LinearChildren(product))
if (factor is Powf(var inner, var minusOne) && minusOne == Number.Integer.MinusOne)
{
if (inner is Mulf or Divf)
foreach (var part in Mulf.LinearChildren(inner))
yield return Inverted(part);
else
yield return Inverted(inner);
}
else
yield return factor;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// past a quartic -- `(e - 4 f x^3)/(e^2 + 4 d f x^2 + 4 e f x^3 + 4 f^2 x^6)`.
if ((answer = IndefiniteIntegralSolver.SolveAsTheArctangentOfAPowerOfXOverABinomial(expr, x, integrateByParts)) is { }) return answer;
// And a fractional or symbolic power of a monomial: `(c x^n)^b` is `c^b x^(n b)`
// for a positive `c`, on the `x > 0` where a symbolic `n` leaves the integrand real.
// for a positive `c`, on the `x > 0` where a fractional `n` leaves the integrand real,
// and a constant times `x^(n b)` on each interval for a symbolic one.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingAPowerOfAMonomial(expr, x, integrateByParts)) is { }) return answer;
// A power of a constant multiple of the radicand a logarithm holds under a root,
// written over that radicand: `acosh(c x)` beside `(d - c^2 d x^2)^(5/2)`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// 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 power of x with a symbolic inner exponent, <c>(c x^m)^p</c>, is
/// <c>K x^(m p)</c> with <c>K = (c x^m)^p/x^(m p)</c> constant, not <c>c^p x^(m p)</c>: a
/// symbolic <c>m</c> can be even, and then the integrand is real for a negative x as well,
/// where <c>x^(m p)</c> is not it. Rubi's 3.5 <c>(ln(a x^n)^m)^p/x</c> was answered
/// <c>ln(a x^n)^(m p + 1)/(n (m p + 1))</c>, with the wrong sign wherever the logarithm is
/// negative at <c>m = 2</c>, <c>p = 1/2</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back at points on both sides of 0, with <c>m = 2</c> and
/// <c>p = 1/2</c>, where the integrand is real there, and with <c>m = 2.3</c> and
/// <c>p = 1.44</c>, past the wrap of the principal argument, where it is complex and
/// compared as a complex number.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class APowerOfAPowerWithASymbolicExponentIntegralTest
{
[Theory]
[InlineData("(x^m)^p")]
[InlineData("(b*x^m)^p")]
[InlineData("x^k*(x^m)^p")]
[InlineData("1/(x*(b*x^m)^p)")]
[InlineData("x^2/(b*x^m)^p")]
[InlineData("(ln(x)^m)^p/x")]
[InlineData("(ln(a*x^n)^m)^p/x")]
[InlineData("(b*ln(a*x^n)^m)^p/x")]
[InlineData("(sin(x)^m)^p*cos(x)")]
public void DifferentiatesBackOnBothSides(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
foreach (var (m, p) in new[] { (2.0, 0.5), (2.3, 1.44) })
{
Entity Pinned(Entity e) => e.Substitute("m", m).Substitute("p", p).Substitute("a", 0.5)
.Substitute("b", 1.5).Substitute("n", 1.3).Substitute("k", 1.2);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { 0.31, 0.57, 1.29, 2.41, -0.43, -1.7, -3.3 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
var difference = got - want;
Assert.True(Math.Abs((double)difference.RealPart) + Math.Abs((double)difference.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}, m = {m}, p = {p}, where the integrand is {want}");
}
}
}

// The constant needs no sign: the factor in front holds for every c.
[Fact]
public void ASymbolicConstantOwesNoCondition()
=> Assert.DoesNotContain("provided", "(b*x^m)^p".ToEntity().Integrate("x").Stringize());
}
}
Loading
Loading