diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1de7240b8..1a7cb79f1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -351,6 +351,19 @@ a second now, as 114, which carry a factor `(1/c)^(-2)/c^2` that is 1. | `"e^(a+b*x+c*x^2)*(b+2*c*x)/(a+b*x+c*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 197 characters | | `"e^(a+b*x+c*x^2)*(b+2*c*x)/(a+b*x+c*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 114 characters | +### A power with x in its base and its exponent is read by the Risch-Norman ansatz as an exponential + +**Answers where there were none.** Rubi's 2.3 `x^(-2 - 1/x) (1 - ln(x))` is `(-x^(-1/x))'`, and was declined, where +`e^(-ln(x)/x) (1 - ln(x))/x^2` was answered by the Risch-Norman ansatz at once. `B^E` is `e^(E ln(B))` on the principal +branch, which is what the power means. With the part of the exponent free of x kept as a power of `B`, the ansatz +now reads it so, and the answer is written back as a power, its factors written on both sides of the bar cancelled +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^(-2-1/x)*(1-ln(x))".ToEntity().Integrate("x")` | `integral(...)` | `-x^(-1/x)`, 17 characters | +| `"x^(-1/x)*(1-ln(x))/x^2".ToEntity().Integrate("x")` | `integral(...)` | 19 characters | + ### A power of a sum beside its derivative is read with the logarithms in it as factors **Answers where there were none.** `(a m x^m + b n q ln(c x^n)^(q - 1))/(x (a x^m + b ln(c x^n)^q))` was declined, and so diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 04a4df921..3960f7384 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -21050,6 +21050,42 @@ private static bool AreProportionalAtSampledPoints(Entity left, Entity right, En { if (!Integration.AnsweringTheQuestionAsked) return null; + // A power with x in its base and its exponent, `B^E`, is `e^(E ln(B))` on the principal + // branch, which is what the power means, and so a monomial of the tower: Rubi's 2.3 + // `x^(-2 - 1/x) (1 - ln(x))` is `(-x^(-1/x))'`, and written so it was declined, where + // `e^(-ln(x)/x) (1 - ln(x))/x^2` is answered at once. + if (expr.Nodes.Any(node => node is Powf(var b, var p) && b != MathS.e && b.ContainsNode(x) && p.ContainsNode(x))) + { + // The part of the exponent free of x stays a power of B: `x^(-2 - 1/x)` is + // `x^(-2) e^(-ln(x)/x)`, a rational function of x times the monomial, which the ansatz + // reads where `e^((-2 - 1/x) ln(x))` is a monomial it has no polynomial in x beside. + var asExponentials = expr.Replace(node => + { + if (node is not Powf(var b, var p) || b == MathS.e || !b.ContainsNode(x) || !p.ContainsNode(x)) + return node; + Entity constantPart = Number.Integer.Zero, variablePart = Number.Integer.Zero; + foreach (var term in Sumf.LinearChildren(p)) + if (term.ContainsNode(x)) + variablePart = variablePart == Number.Integer.Zero ? term : variablePart + term; + else + constantPart = constantPart == Number.Integer.Zero ? term : constantPart + term; + var exponential = MathS.Pow(MathS.e, (variablePart * MathS.Ln(b)).InnerSimplified); + return constantPart == Number.Integer.Zero ? exponential : MathS.Pow(b, constantPart) * exponential; + }); + // And written back: `e^(q ln(B))` is `B^q` again, as the integrand had it. + return SolveByRischNormanAnsatzAsWritten(asExponentials, x) is { } inExponentials + && inExponentials.Replace(node => node is Powf(var e0, Mulf(var q, Logf(var lb, var b))) && e0 == MathS.e && lb == MathS.e && b.ContainsNode(x) + ? MathS.Pow(b, q) : node).InnerSimplified is var writtenBack + && Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(writtenBack)) is var (top, bottom) + // A factor written on both sides of the bar, `-x x^(-1/x)/x`, cancelled. + && CancelCommonFactors(top, bottom) is var answer + && Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + return SolveByRischNormanAnsatzAsWritten(expr, x); + } + + private static Entity? SolveByRischNormanAnsatzAsWritten(Entity expr, Entity.Variable x) + { // The transcendental monomials: every exponential and natural logarithm of // something in x, innermost first, each read in the indeterminates of the ones // inside it. diff --git a/Sources/Tests/UnitTests/Calculus/APowerWithTheVariableInItsExponentForTheRischNormanAnsatzIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerWithTheVariableInItsExponentForTheRischNormanAnsatzIntegralTest.cs new file mode 100644 index 000000000..214b8c1e6 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerWithTheVariableInItsExponentForTheRischNormanAnsatzIntegralTest.cs @@ -0,0 +1,44 @@ +// +// 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 +{ + /// + /// A power with the variable in its base and its exponent read by the Risch-Norman ansatz as the + /// exponential it means, B^E = e^(E ln(B)), the part of the exponent free of x kept as a power: + /// Rubi's 2.3 x^(-2 - 1/x) (1 - ln(x)) is (-x^(-1/x))', and as written it was declined. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APowerWithTheVariableInItsExponentForTheRischNormanAnsatzIntegralTest + { + [Theory] + [InlineData("x^(-2 - 1/x)*(1 - ln(x))")] + [InlineData("x^(-1/x)*(1 - ln(x))/x^2")] + public void IsAnsweredAsAPower(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 40, $"the antiderivative of {integrand} is {text}"); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + foreach (var at in new[] { 0.3, 0.7, 1.3, 2.2, 3.1 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + 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}"); + } + } + } +}