Skip to content

Commit 7df6694

Browse files
Take a whole power out of a power of a product, in front of a constant (#1875)
* Take a whole power out of a power of a product, in front of a constant (c R f^n)^r is K f^(n r) R^r, with K constant wherever it is defined. And an odd root is the real root, so a constant taken out of one needs no sign. Rubi 4.1.12's (c sin(a + b x^n)^3)^(k/3) rows and 1.3.2:1192, 1193, 1248. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the whole powers' new answers in BREAKING-CHANGES.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f68bebd commit 7df6694

4 files changed

Lines changed: 197 additions & 1 deletion

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ partial fractions divide. It is taken out in front of the integral first
308308
| `"(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 |
309309
| `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters |
310310

311+
### A whole power is taken out of a power of a product, in front of a constant
312+
313+
**Answers where there were none.** `(c sin(a + b x)^3)^(1/3)/x` was declined: nothing read a function under a root of
314+
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,
315+
and only a factor of the integrand is rewritten. A constant stays under the root inside `K`, so a negative `c` is
316+
answered too: an odd root is the real root
317+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
318+
319+
| Input | Was (2.5.0) | Now |
320+
|---|---|---|
321+
| `"(c*sin(a + b*x)^3)^(1/3)".ToEntity().Integrate("x")` | `integral(...)` | 35 characters |
322+
| `"(c*sin(a + b*x)^3)^(2/3)".ToEntity().Integrate("x")` | `integral(...)` | 78 characters |
323+
| `"(c*sin(a + b*x)^3)^(1/3)/x".ToEntity().Integrate("x")` | `integral(...)` | 105 characters |
324+
| `"(c*sin(a + b*x)^3)^(2/3)/x^3".ToEntity().Integrate("x")` | `integral(...)` | 391 characters |
325+
| `"(c*sin(a + b*x^n)^3)^(1/3)/x".ToEntity().Integrate("x")` | `integral(...)` | 129 characters |
326+
| `"sqrt((x^2 - 1)^2/(x*(1 + x^2)))/(1 + x^2)".ToEntity().Integrate("x")` | `integral(...)` | 167 characters |
327+
| `"1/sqrt((a^2 + x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 104 characters |
328+
| `"1/sqrt((a + b*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 100 characters |
329+
| `"1/sqrt(1 - coth(x)^2)".ToEntity().Integrate("x")` | `integral(...)` | 301 characters |
330+
311331
### A power of x past the twelfth beside a block is split off
312332

313333
**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

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19031,6 +19031,95 @@ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out
1903119031
}
1903219032
}
1903319033

19034+
/// <summary>
19035+
/// A power of a product with a factor under a whole power, that power taken out:
19036+
/// <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
19037+
/// <c>c</c> is free of <c>x</c>. The logarithmic derivatives of both sides are
19038+
/// <c>r (R'/R + n f'/f)</c>, so <c>K</c> is
19039+
/// constant wherever it is defined, and it stands in front of the answer the way Rubi
19040+
/// writes <c>(c sin(a + b x)^3)^(1/3) csc(a + b x)</c> in front of its.
19041+
/// </summary>
19042+
/// <remarks>
19043+
/// <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
19044+
/// <c>1/sqrt((a^2 + x^2)^3)</c> were declined: nothing reads a function under a root of its
19045+
/// own power. With the power taken out each is <c>K</c> times an integrand the chain
19046+
/// answers. After every other rule, and at the question asked only.
19047+
/// https://github.com/asc-community/AngouriMath/issues/718
19048+
/// </remarks>
19049+
internal static Entity? SolveByTakingTheWholePowersOutOfAPowerOfAProduct(Entity expr, Entity.Variable x, bool integrateByParts)
19050+
{
19051+
if (!Integration.AnsweringTheQuestionAsked || takingTheWholePowersOut)
19052+
return null;
19053+
var multiple = Variable.CreateUnique(expr, "k_out");
19054+
Entity? constant = null;
19055+
var rewritten = AFactor(expr);
19056+
if (constant is null)
19057+
return null;
19058+
var simplified = rewritten.InnerSimplified;
19059+
Entity? answer;
19060+
takingTheWholePowersOut = true;
19061+
try
19062+
{
19063+
answer = Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts);
19064+
}
19065+
finally
19066+
{
19067+
takingTheWholePowersOut = false;
19068+
}
19069+
if (answer is null)
19070+
return null;
19071+
answer = answer.Substitute(multiple, constant);
19072+
return answer.ContainsNode(multiple) ? null : answer;
19073+
19074+
// Only a factor of the integrand: inside a function, `ln(1/(c x) + sqrt(1/(c^2 x^2) - 1))`
19075+
// for `asech(c x)`, K is not a factor of anything, and the question it writes is no shorter.
19076+
Entity AFactor(Entity node) => node switch
19077+
{
19078+
Mulf(var left, var right) => AFactor(left) * AFactor(right),
19079+
Divf(var above, var below) => AFactor(above) / AFactor(below),
19080+
_ => TakeOut(node),
19081+
};
19082+
19083+
Entity TakeOut(Entity node)
19084+
{
19085+
if (constant is not null || node is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer
19086+
|| !@base.ContainsNode(x))
19087+
return node;
19088+
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(@base));
19089+
Entity outside = Number.Integer.One;
19090+
Entity inside = Number.Integer.One;
19091+
var taken = false;
19092+
foreach (var (side, sign) in new[] { (above, 1), (below, -1) })
19093+
foreach (var factor in Mulf.LinearChildren(side))
19094+
{
19095+
var (factorBase, factorExponent) = factor switch
19096+
{
19097+
Powf(var b, Number.Integer n) => (b, n),
19098+
_ => (factor, Number.Integer.One),
19099+
};
19100+
if (!factorBase.ContainsNode(x))
19101+
continue;
19102+
if (factorExponent.EInteger.Abs().CompareTo(EInteger.FromInt32(2)) >= 0)
19103+
{
19104+
outside *= MathS.Pow(factorBase, (sign * factorExponent * exponent).InnerSimplified);
19105+
taken = true;
19106+
}
19107+
else
19108+
inside = sign > 0 ? inside * factor : inside / factor;
19109+
}
19110+
if (!taken)
19111+
return node;
19112+
// A factor free of x stays under the power, in K: taken out, `c^(1/3)` is only
19113+
// `c`'s cube root for `c > 0`.
19114+
var written = inside == Number.Integer.One ? outside : outside * MathS.Pow(inside, exponent);
19115+
constant = node / written;
19116+
return multiple * written;
19117+
}
19118+
}
19119+
19120+
/// <summary>Set while <see cref="SolveByTakingTheWholePowersOutOfAPowerOfAProduct"/> asks its question.</summary>
19121+
[System.ThreadStatic] private static bool takingTheWholePowersOut;
19122+
1903419123
/// <summary>
1903519124
/// A rational function of <c>x</c> and one square root of a quadratic in <c>x</c>,
1903619125
/// rationalised by an Euler substitution and handed to the rational integrator.
@@ -28451,7 +28540,11 @@ Entity Rewritten(Entity side, Number.Integer sign)
2845128540
if (constantAlone && (constant == Number.Integer.One || restFactors > 1
2845228541
|| !EntersOnlyThroughTrigonometricFunctions(single!, x)))
2845328542
return node;
28454-
if (constant != Number.Integer.One && constant.Evaled is not Number.Real { IsPositive: true })
28543+
// An odd denominator is the real root, `(-8)^(1/3) = -2`, which splits across a
28544+
// product for any real constant: `(c sin(x)^3)^(1/3)` is `c^(1/3) sin(x)` for a
28545+
// negative `c` as well, and asking `c > 0` left it undefined there.
28546+
var aRealRoot = exponent is Number.Rational { ERational: var fraction } && !fraction.Denominator.IsEven;
28547+
if (constant != Number.Integer.One && constant.Evaled is not Number.Real { IsPositive: true } && !aRealRoot)
2845528548
{
2845628549
if (constant.Vars.Any() is false)
2845728550
return node;

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
12001200
// And a sum with the root below the bar, over its conjugate: after every rule above,
12011201
// which answer the numeric spellings, `1/(a c + b c x^2 + d sqrt(a + b x^2))` among them.
12021202
if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(expr, x, integrateByParts)) is { }) return answer;
1203+
// And a power of a product with a factor under a whole power, that power taken out in
1204+
// front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`.
1205+
if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer;
12031206
// Product-to-sum among the rewrites rather than before them, because a product of
12041207
// trigonometric functions of *equal* arguments is a power and wants a different tool;
12051208
// this only fires where the arguments differ, which is exactly what every substitution
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A power of a product with a factor under a whole power, that power taken out in front of
16+
/// a constant: <c>(c sin(a + b x)^3)^(1/3)</c> is <c>K sin(a + b x)</c>.
17+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
18+
/// </summary>
19+
/// <remarks>
20+
/// Rubi's 4.1.12 and 1.3.2 were declined. Each answer is differentiated back with the constant
21+
/// under the root on both signs: an odd root is the real root, and a negative <c>c</c> used to
22+
/// be answered <c>provided c &gt; 0</c>.
23+
/// </remarks>
24+
[Trait("Area", "Calculus")]
25+
public sealed class AWholePowerTakenOutOfAPowerOfAProductIntegralTest
26+
{
27+
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
28+
{
29+
var integral = integrand.ToEntity().Integrate("x");
30+
Assert.DoesNotContain("integral(", integral.Stringize());
31+
Assert.DoesNotContain("NaN", integral.Stringize());
32+
33+
var derivative = integral.Substitute("C", 0).Differentiate("x");
34+
Entity original = integrand.ToEntity();
35+
foreach (var (name, value) in pins)
36+
{
37+
derivative = derivative.Substitute(name, value);
38+
original = original.Substitute(name, value);
39+
}
40+
41+
var compared = 0;
42+
foreach (var at in points)
43+
{
44+
var got = derivative.Substitute("x", at).EvalNumerical();
45+
var want = original.Substitute("x", at).EvalNumerical();
46+
if (got.IsNaN || want.IsNaN)
47+
continue;
48+
compared++;
49+
var difference = Math.Abs((double)(got - want).RealPart)
50+
+ Math.Abs((double)(got - want).ImaginaryPart);
51+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart));
52+
Assert.True(difference / scale < 1e-8,
53+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, "
54+
+ $"where the integrand is {want}");
55+
}
56+
Assert.True(compared >= 3,
57+
$"only {compared} of {points.Length} points were comparable for {integrand}");
58+
}
59+
60+
[Theory]
61+
[InlineData("(c*sin(a+b*x)^3)^(1/3)")]
62+
[InlineData("(c*sin(a+b*x)^3)^(2/3)")]
63+
[InlineData("(c*sin(a+b*x)^3)^(1/3)/x")]
64+
[InlineData("(c*sin(a+b*x)^3)^(2/3)/x^3")]
65+
[InlineData("(c*sin(a+b*x^2)^3)^(1/3)/x^3")]
66+
public void AConstantOfEitherSignUnderAnOddRoot(string integrand)
67+
{
68+
var points = new[] { -1.4, -0.3, 0.8, 2.25 };
69+
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", 2));
70+
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", -3));
71+
}
72+
73+
[Theory]
74+
[InlineData("sqrt((-1+x^2)^2/(x*(1+x^2)))/(1+x^2)", new[] { 0.5, 0.8, 2.0, 3.0 })]
75+
[InlineData("1/sqrt((a^2+x^2)^3)", new[] { -2.0, 0.3, 2.5 })]
76+
[InlineData("1/sqrt((a+b*x^2)^3)", new[] { -1.3, 0.3, 1.1 })]
77+
public void ARootOfAPowerOfAPolynomial(string integrand, double[] points)
78+
=> DifferentiatesBack(integrand, points, ("a", 2), ("b", -1));
79+
}
80+
}

0 commit comments

Comments
 (0)