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}"); + } + } +}