Skip to content

Commit f7d7874

Browse files
Read a sum of products of powers with a base a term lacks there to the power 0 (#2019)
WithTheCommonPowersOut reads a sum whose terms are a constant times powers of the same bases as the bases' common product times a bracket, which the derivative of a product then answers. It asked every term for the same bases. A base missing from a term is now counted there to the power 0 where its exponents elsewhere are whole, so (1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x), with x and 1 + e^x in the first term only, is (e^x + x)^(-1/2) ((1 + e^x) x + 2 (e^x + x)), the derivative of 2 x sqrt(e^x + x). Master's reading of a raised function in some terms only (#1980) and of x to the power 0 (#1984) are kept, and a raised function is now one such base. Rubi's 1.3.2:1256 and 2.3:866 and 871, declined by 2.5.0 and by master f7f2a7a, are answered: 2 x sqrt(e^x + x), 2/5 x^2 sqrt(5 e^x + x^3) and -1/5 (1 - x^6)^(5/3) x^(-5). The other 15 rows its chain moved against 4877ed2 answer the same on master f7f2a7a and here, or are past 20 s on both (Rubi's 3.3:245-247, which this does not reach). Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f7f2a7a commit f7d7874

3 files changed

Lines changed: 101 additions & 17 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,21 @@ and split over all `n` roots of the binomial as the reciprocal alone already was
11971197
| `"sinh(c+d*x)^6/(a+b*sinh(c+d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 1,517 characters |
11981198
| `"sin(x)^2/(a+b*sin(x)^5)".ToEntity().Integrate("x")` | `integral(...)` | 2,475 characters |
11991199

1200+
### A sum of products of powers is read with a base a term lacks there to the power 0
1201+
1202+
**Answers where there were none.** A sum of products of powers is read as the bases' common product times a bracket,
1203+
which the derivative of a product answers. A base missing from a term is now counted there to the power 0, where its
1204+
exponents elsewhere are whole: `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is
1205+
`(e^x + x)^(-1/2) ((1 + e^x) x + 2 (e^x + x))`, the derivative of `2 x sqrt(e^x + x)`, and with the bases of every term
1206+
asked to be the same, `x` and `1 + e^x` in the first term only, nothing read it. Rubi's 1.3.2 and 2.3
1207+
([#233](https://github.com/asc-community/AngouriMath/issues/233)).
1208+
1209+
| Input | Was (2.5.0) | Now |
1210+
|---|---|---|
1211+
| `"(1 + e^x)*x/sqrt(e^x + x) + 2*sqrt(e^x + x)".ToEntity().Integrate("x")` | `integral(...)` | `2 x sqrt(e^x + x)` |
1212+
| `"1/5*x^2*(5*e^x + 3*x^2)/sqrt(5*e^x + x^3) + 4/5*x*sqrt(5*e^x + x^3)".ToEntity().Integrate("x")` | `integral(...)` | `2/5 x^2 sqrt(5 e^x + x^3)` |
1213+
| `"(1 - x^6)^(2/3) + (1 - x^6)^(2/3)/x^6".ToEntity().Integrate("x")` | `integral(...)` | `-1/5 (1 - x^6)^(5/3) x^(-5)` |
1214+
12001215
### A rational function times a logarithm of nested powers of linears is integrated by parts
12011216

12021217
**Answers where there were none.** `ln(e (f (a + b x)^p (c + d x)^q)^r)` was declined, and so were four more of Rubi's

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

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17541,11 +17541,12 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L)
1754117541
private const int MaximumTowerDegree = 4;
1754217542

1754317543
/// <summary>
17544-
/// <paramref name="sum"/>, every term of which is a constant times powers of the same
17545-
/// bases with <paramref name="x"/> in them, as the product of those bases to the least of
17546-
/// each one's exponents times the sum of what is left. Null where a base is not in every
17547-
/// term, where no exponent of one is the least, or where the least are all whole, which
17548-
/// the rational rules read.
17544+
/// <paramref name="sum"/>, every term of which is a constant times powers of bases with
17545+
/// <paramref name="x"/> in them, as the product of those bases to the least of each one's
17546+
/// exponents times the sum of what is left, a base missing from a term counted there to the
17547+
/// power 0. Null where a base with an exponent that is not whole is not in every term,
17548+
/// where no exponent of one is the least, or where the least are all whole, which the
17549+
/// rational rules read.
1754917550
/// </summary>
1755017551
private static Entity? WithTheCommonPowersOut(Entity sum, Entity.Variable x)
1755117552
{
@@ -17581,16 +17582,30 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L)
1758117582
}
1758217583
if (terms.Count < 2)
1758317584
return null;
17584-
// The bases in every term; a function in some of them only -- a sine or cosine, an
17585-
// exponential or a sum of them -- stays in the bracket, where the rule raises it from
17586-
// nothing: `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for
17585+
// The bases of every term. A base missing from a term is there to the power 0, where its
17586+
// exponents elsewhere are whole: `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is
17587+
// `(e^x + x)^(-1/2) ((1 + e^x) x + 2 (e^x + x))`, the derivative of `2 x sqrt(e^x + x)`,
17588+
// and with the bases of every term asked to be the same, nothing read it. A function in
17589+
// some terms only -- a sine or cosine, an exponential or a sum of them -- is so raised
17590+
// from nothing in the bracket, and the least exponents may then all be whole:
17591+
// `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for
1758717592
// `R = x^3 + 3 sin(a + b x)`, is `x/sqrt(R)` times a bracket with the cosine in it.
17588-
var bases = terms[0].Powers.Keys.Where(@base => terms.All(term => term.Powers.ContainsKey(@base))).ToList();
17593+
var bases = terms.SelectMany(term => term.Powers.Keys).Distinct().ToList();
17594+
var functionsInSomeTerms = bases.Any(@base => !terms.All(term => term.Powers.ContainsKey(@base))
17595+
&& terms.All(term => !term.Powers.TryGetValue(@base, out var exponent) || exponent == Number.Integer.One)
17596+
&& IsAFunctionToRaise(@base, x));
1758917597
if (bases.Count == 0)
1759017598
return null;
17591-
var functionsInSomeTerms = terms.Any(term => term.Powers.Count != bases.Count);
17592-
if (functionsInSomeTerms && terms.Any(term => term.Powers.Any(pair => !bases.Contains(pair.Key) && (pair.Value != Number.Integer.One || !IsAFunctionToRaise(pair.Key, x)))))
17593-
return null;
17599+
foreach (var @base in bases)
17600+
{
17601+
if (terms.All(term => term.Powers.ContainsKey(@base)))
17602+
continue;
17603+
if (terms.Any(term => term.Powers.TryGetValue(@base, out var exponent) && exponent is not Number.Integer))
17604+
return null;
17605+
foreach (var (_, powers) in terms)
17606+
if (!powers.ContainsKey(@base))
17607+
powers[@base] = Number.Integer.Zero;
17608+
}
1759417609
// For each base the least of its exponents, against which every other is more by a
1759517610
// whole number or by a sum of symbols with no sign in front: `x^(m - 1)` beside
1759617611
// `x^(m + n - 1)` leaves `x^n` in the bracket, as the derivative of `a + b x^n` does.
@@ -17625,14 +17640,12 @@ static bool More(Entity difference)
1762517640
foreach (var @base in bases)
1762617641
if (Difference(powers[@base], least[@base]) is var raised && raised != Number.Integer.Zero)
1762717642
rest = rest * (raised == Number.Integer.One ? @base : MathS.Pow(@base, raised));
17628-
foreach (var function in powers.Keys)
17629-
if (!bases.Contains(function))
17630-
rest = rest * function;
1763117643
bracket = bracket == Number.Integer.Zero ? rest : bracket + rest;
1763217644
}
1763317645
Entity common = Number.Integer.One;
1763417646
foreach (var @base in bases)
17635-
common = common * MathS.Pow(@base, least[@base]);
17647+
if (least[@base] != Number.Integer.Zero)
17648+
common = common * MathS.Pow(@base, least[@base]);
1763617649
return common * bracket;
1763717650
}
1763817651

@@ -17850,7 +17863,8 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R
1785017863
// bracket, which is the shape read below: `m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1)
1785117864
// (a + b x^n)^(p - 1)` is `x^(m - 1) (a + b x^n)^(p - 1) (m (a + b x^n) + b n p x^n)`,
1785217865
// the derivative of `x^m (a + b x^n)^p`. As a sum it was split, and neither term is
17853-
// elementary.
17866+
// elementary. A base with whole exponents may be missing from some terms: Rubi's
17867+
// `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is `(2 x sqrt(e^x + x))'`.
1785417868
if (expr is Sumf or Minusf)
1785517869
return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x, integrateByParts) : null;
1785617870
// A bracket that is a polynomial with a power of x in every term has that power taken out, to
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
/// The derivative of a product of powers written out as a sum whose terms do not all hold the
16+
/// same bases: <c>(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)</c> is <c>(2 x sqrt(e^x + x))'</c>,
17+
/// with <c>x</c> and <c>1 + e^x</c> in the first term only. Neither term is elementary on its
18+
/// own, and the sum was declined. Rubi's 2.3:866-877.
19+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
20+
/// </summary>
21+
[Trait("Area", "Calculus")]
22+
public sealed class ADerivativeOfAProductOfPowersWrittenAsASumIntegralTest
23+
{
24+
[Theory]
25+
[InlineData("(1 + e^x)*x/sqrt(e^x + x) + 2*sqrt(e^x + x)")]
26+
[InlineData("x/sqrt(e^x + x) + e^x*x/sqrt(e^x + x) + 2*sqrt(e^x + x)")]
27+
[InlineData("1/5*x^2*(5*e^x + 3*x^2)/sqrt(5*e^x + x^3) + 4/5*x*sqrt(5*e^x + x^3)")]
28+
[InlineData("(-1)/(e^x + x)^(1/3) + x/(e^x + x)^(1/3) - (e^x + x)^(2/3)")]
29+
[InlineData("2*x/(e^x + x)^(1/3) + 2*e^x*x/(e^x + x)^(1/3) + 3*(e^x + x)^(2/3)")]
30+
[InlineData("x*(a*e^x + b)/sqrt(a*e^x + b*x) + 2*sqrt(a*e^x + b*x)")]
31+
public void IsTheProductRaised(string integrand)
32+
{
33+
var integral = integrand.ToEntity().Integrate("x");
34+
var text = integral.Stringize();
35+
Assert.DoesNotContain("integral(", text);
36+
Assert.True(text.Length < 200, $"{text.Length} characters of answer for {integrand}: {text}");
37+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7);
38+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
39+
var original = Pinned(integrand.ToEntity());
40+
var compared = 0;
41+
foreach (var at in new[] { -0.4, 0.3, 0.8, 1.3, 2.1, 2.9 })
42+
{
43+
var want = original.Substitute("x", at).EvalNumerical();
44+
var got = derivative.Substitute("x", at).EvalNumerical();
45+
if (want.IsNaN)
46+
continue;
47+
compared++;
48+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
49+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
50+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
51+
}
52+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)