diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 0909ba42e..1d639d541 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -1197,6 +1197,21 @@ and split over all `n` roots of the binomial as the reciprocal alone already was
| `"sinh(c+d*x)^6/(a+b*sinh(c+d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 1,517 characters |
| `"sin(x)^2/(a+b*sin(x)^5)".ToEntity().Integrate("x")` | `integral(...)` | 2,475 characters |
+### A sum of products of powers is read with a base a term lacks there to the power 0
+
+**Answers where there were none.** A sum of products of powers is read as the bases' common product times a bracket,
+which the derivative of a product answers. A base missing from a term is now counted there to the power 0, where its
+exponents elsewhere are whole: `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is
+`(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
+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
+([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(1 + e^x)*x/sqrt(e^x + x) + 2*sqrt(e^x + x)".ToEntity().Integrate("x")` | `integral(...)` | `2 x sqrt(e^x + x)` |
+| `"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)` |
+| `"(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)` |
+
### A rational function times a logarithm of nested powers of linears is integrated by parts
**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
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 31dc3156e..867b1819f 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -17541,11 +17541,12 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L)
private const int MaximumTowerDegree = 4;
///
- /// , every term of which is a constant times powers of the same
- /// bases with in them, as the product of those bases to the least of
- /// each one's exponents times the sum of what is left. Null where a base is not in every
- /// term, where no exponent of one is the least, or where the least are all whole, which
- /// the rational rules read.
+ /// , every term of which is a constant times powers of bases with
+ /// in them, as the product of those bases to the least of each one's
+ /// exponents times the sum of what is left, a base missing from a term counted there to the
+ /// power 0. Null where a base with an exponent that is not whole is not in every term,
+ /// where no exponent of one is the least, or where the least are all whole, which the
+ /// rational rules read.
///
private static Entity? WithTheCommonPowersOut(Entity sum, Entity.Variable x)
{
@@ -17581,16 +17582,30 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L)
}
if (terms.Count < 2)
return null;
- // The bases in every term; a function in some of them only -- a sine or cosine, an
- // exponential or a sum of them -- stays in the bracket, where the rule raises it from
- // nothing: `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for
+ // The bases of every term. A base missing from a term is there to the power 0, where its
+ // exponents elsewhere are whole: `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is
+ // `(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 asked to be the same, nothing read it. A function in
+ // some terms only -- a sine or cosine, an exponential or a sum of them -- is so raised
+ // from nothing in the bracket, and the least exponents may then all be whole:
+ // `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for
// `R = x^3 + 3 sin(a + b x)`, is `x/sqrt(R)` times a bracket with the cosine in it.
- var bases = terms[0].Powers.Keys.Where(@base => terms.All(term => term.Powers.ContainsKey(@base))).ToList();
+ var bases = terms.SelectMany(term => term.Powers.Keys).Distinct().ToList();
+ var functionsInSomeTerms = bases.Any(@base => !terms.All(term => term.Powers.ContainsKey(@base))
+ && terms.All(term => !term.Powers.TryGetValue(@base, out var exponent) || exponent == Number.Integer.One)
+ && IsAFunctionToRaise(@base, x));
if (bases.Count == 0)
return null;
- var functionsInSomeTerms = terms.Any(term => term.Powers.Count != bases.Count);
- if (functionsInSomeTerms && terms.Any(term => term.Powers.Any(pair => !bases.Contains(pair.Key) && (pair.Value != Number.Integer.One || !IsAFunctionToRaise(pair.Key, x)))))
- return null;
+ foreach (var @base in bases)
+ {
+ if (terms.All(term => term.Powers.ContainsKey(@base)))
+ continue;
+ if (terms.Any(term => term.Powers.TryGetValue(@base, out var exponent) && exponent is not Number.Integer))
+ return null;
+ foreach (var (_, powers) in terms)
+ if (!powers.ContainsKey(@base))
+ powers[@base] = Number.Integer.Zero;
+ }
// For each base the least of its exponents, against which every other is more by a
// whole number or by a sum of symbols with no sign in front: `x^(m - 1)` beside
// `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)
foreach (var @base in bases)
if (Difference(powers[@base], least[@base]) is var raised && raised != Number.Integer.Zero)
rest = rest * (raised == Number.Integer.One ? @base : MathS.Pow(@base, raised));
- foreach (var function in powers.Keys)
- if (!bases.Contains(function))
- rest = rest * function;
bracket = bracket == Number.Integer.Zero ? rest : bracket + rest;
}
Entity common = Number.Integer.One;
foreach (var @base in bases)
- common = common * MathS.Pow(@base, least[@base]);
+ if (least[@base] != Number.Integer.Zero)
+ common = common * MathS.Pow(@base, least[@base]);
return common * bracket;
}
@@ -17850,7 +17863,8 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R
// bracket, which is the shape read below: `m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1)
// (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)`,
// the derivative of `x^m (a + b x^n)^p`. As a sum it was split, and neither term is
- // elementary.
+ // elementary. A base with whole exponents may be missing from some terms: Rubi's
+ // `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is `(2 x sqrt(e^x + x))'`.
if (expr is Sumf or Minusf)
return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x, integrateByParts) : null;
// A bracket that is a polynomial with a power of x in every term has that power taken out, to
diff --git a/Sources/Tests/UnitTests/Calculus/ADerivativeOfAProductOfPowersWrittenAsASumIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ADerivativeOfAProductOfPowersWrittenAsASumIntegralTest.cs
new file mode 100644
index 000000000..c194c9a64
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ADerivativeOfAProductOfPowersWrittenAsASumIntegralTest.cs
@@ -0,0 +1,55 @@
+//
+// 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
+{
+ ///
+ /// The derivative of a product of powers written out as a sum whose terms do not all hold the
+ /// same bases: (1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x) is (2 x sqrt(e^x + x))',
+ /// with x and 1 + e^x in the first term only. Neither term is elementary on its
+ /// own, and the sum was declined. Rubi's 2.3:866-877.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ADerivativeOfAProductOfPowersWrittenAsASumIntegralTest
+ {
+ [Theory]
+ [InlineData("(1 + e^x)*x/sqrt(e^x + x) + 2*sqrt(e^x + x)")]
+ [InlineData("x/sqrt(e^x + x) + e^x*x/sqrt(e^x + x) + 2*sqrt(e^x + x)")]
+ [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)")]
+ [InlineData("(-1)/(e^x + x)^(1/3) + x/(e^x + x)^(1/3) - (e^x + x)^(2/3)")]
+ [InlineData("2*x/(e^x + x)^(1/3) + 2*e^x*x/(e^x + x)^(1/3) + 3*(e^x + x)^(2/3)")]
+ [InlineData("x*(a*e^x + b)/sqrt(a*e^x + b*x) + 2*sqrt(a*e^x + b*x)")]
+ public void IsTheProductRaised(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 200, $"{text.Length} characters of answer for {integrand}: {text}");
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -0.4, 0.3, 0.8, 1.3, 2.1, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).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}, where the integrand is {want}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}