diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 04230258d..3e339f087 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -2155,6 +2155,21 @@ secant too now, and the integrand is `sgn(cos(x)) (1 + u^2)^(3/2)/(a + b u)^2` i | `"sec(x)^7/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,253 characters | | `"sec(x)/(a + b*cot(x))".ToEntity().Integrate("x")` | `integral(...)` | 571 characters | +### A quotient by a radical sum with a constant conjugate product is written over the conjugate + +**Answers where there were none.** `x^4/e^asech(a x)` was declined, and so were the powers of `x` below it, Rubi's +7.5.2. `e^asech(a x)` arrives as `1/(a x) + sqrt(1/(a x)^2 - 1)`, whose product with its conjugate is 1, so the +quotient is `x^m (1/(a x) - sqrt(1/(a x)^2 - 1))`, a sum with no quotient left. `(A + B sqrt(R))(A - B sqrt(R)) = +A^2 - B^2 R` holds on every branch, so the rewrite holds wherever the quotient is defined. Only for a constant +product, and only for the question asked: by parts against `asech` or `acsch` asks a quotient by the same sum, which +the rules below answer as written ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^4/e^asech(a*x)".ToEntity().Integrate("x")` | `integral(...)` | 284 characters | +| `"x/e^asech(a*x)".ToEntity().Integrate("x")` | `integral(...)` | 268 characters | +| `"1/(e^asech(a*x)*x)".ToEntity().Integrate("x")` | `integral(...)` | 366 characters | + ### A sum with the root of a quadratic below the bar is cleared by its conjugate **Answers where there were none.** `1/(a c + b c x^2 + d sqrt(a + b x^2))` was declined: Euler's substitution diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f47f57a9b..b9d29f8da 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -1221,6 +1221,74 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab } } + /// + /// A quotient by A + B sqrt(R) whose conjugate product A^2 - B^2 R is a constant + /// c, written over the conjugate: N/(A + B sqrt(R)) = N (A - B sqrt(R))/c, a sum + /// of terms without the quotient. e^asech(a x) arrives as + /// 1/(a x) + sqrt(1/(a x)^2 - 1), whose conjugate product is 1, so x^m/e^asech(a x) + /// is x^m (1/(a x) - sqrt(1/(a x)^2 - 1)). Rubi's 7.5.2. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// The search took the quotient as written, a root below the bar beside a pole, and ran past + /// the budget. The rewrite is the identity (A + B sqrt(R))(A - B sqrt(R)) = A^2 - B^2 R, + /// with sqrt(R)^2 = R on every branch, so it holds wherever the quotient is defined. + /// Only for a constant product: otherwise the quotient by it is the harder question. + /// + internal static Entity? SolveByTheConjugateOfARadicalSumBelowTheBar(Entity expr, Entity.Variable x, bool integrateByParts) + { + // Only the question asked: by parts against asech or acsch asks a quotient by the same sum, + // which the routes below answer as written and not as the sum over the conjugate. + if (!Integration.AnsweringTheQuestionAsked || expr is not Divf) + return null; + var factors = FactorsOfTheIntegrand(expr); + for (var chosen = 0; chosen < factors.Count; chosen++) + { + var (factor, underneath) = factors[chosen]; + if (!underneath || factor is not (Sumf or Minusf) || !factor.ContainsNode(x)) + continue; + // A + B sqrt(R), the root's terms gathered. + Entity a = Number.Integer.Zero, b = Number.Integer.Zero; + Entity? radicand = null; + var readable = true; + foreach (var term in Sumf.LinearChildren(factor)) + { + Entity? root = null; + Entity rest = Number.Integer.One; + foreach (var part in Mulf.LinearChildren(term)) + { + if (root is null && part is Powf(var q, Number.Rational half) && half == Number.Rational.Create(1, 2) && q.ContainsNode(x)) + root = q; + else + rest *= part; + } + if (root is null) + a += term; + else if (radicand is null || radicand == root) + { + radicand = root; + b += rest; + } + else + readable = false; + } + if (!readable || radicand is null || a == Number.Integer.Zero || b.ContainsNode(x) && b.Nodes.Any(node => node is Powf(_, Number.Rational { ERational.Denominator: var den }) && !den.Equals(EInteger.One))) + continue; + var product = a * a - b * b * radicand; + // A constant: the same at a point and identically so. + var atAPoint = Functions.PartialFractions.Bare(product.Substitute(x, Number.Rational.Create(37, 23)).Simplify()); + if (atAPoint.ContainsNode(x) || VanishesIdentically(atAPoint) || !VanishesIdentically(product - atAPoint)) + continue; + Entity rest2 = Number.Integer.One; + for (var other = 0; other < factors.Count; other++) + if (other != chosen) + rest2 = factors[other].Underneath ? rest2 / factors[other].Factor : rest2 * factors[other].Factor; + var rewritten = rest2 * (a - b * MathS.Sqrt(radicand)) / atAPoint; + return Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts); + } + return null; + } + /// /// A quotient by the square of a base, by parts against the base's reciprocal, where the /// base's derivative makes the rest exact: N/(M v^2) is g v'/v^2 with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 7d482b645..53bb299b5 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -793,6 +793,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheHyperbolicTangent(expr, x, integrateByParts)) is { }) return answer; // A power of a quotient of linears over a half-odd power of their product, by Liouville's ansatz. if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAQuotientOfLinearsOverAHalfOddPowerOfTheirProduct(expr, x)) is { }) return answer; + // A quotient by a radical sum whose conjugate product is a constant, over the conjugate. + if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfARadicalSumBelowTheBar(expr, x, integrateByParts)) is { }) return answer; // A power of an exponential with a positive base is the exponential of the product, // exactly, and only that spelling is one the exponential rules read. if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AQuotientByARadicalSumWithAConstantConjugateProductIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AQuotientByARadicalSumWithAConstantConjugateProductIntegralTest.cs new file mode 100644 index 000000000..3129ee863 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AQuotientByARadicalSumWithAConstantConjugateProductIntegralTest.cs @@ -0,0 +1,60 @@ +// +// 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 quotient by A + B sqrt(R) whose conjugate product is a constant, over the conjugate: + /// e^asech(a x) arrives as 1/(a x) + sqrt(1/(a x)^2 - 1), whose conjugate product + /// is 1, and x^m/e^asech(a x) timed out. Rubi's 7.5.2. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AQuotientByARadicalSumWithAConstantConjugateProductIntegralTest + { + [Theory] + [InlineData("x/e^asech(a*x)")] + [InlineData("x^3/e^asech(a*x)")] + [InlineData("1/(e^asech(a*x)*x)")] + public void OverTheConjugate(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("a", 1.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + // Where asech(1.3 x) is real: 0 < x < 1/1.3. + foreach (var at in new[] { 0.05, 0.1, 0.2, 0.35, 0.5, 0.7 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN || got.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}"); + } + + // By parts against asech or acsch asks a quotient by the same sum; written over the conjugate + // below the top, these ran past the budget where they are answered at once as written. + [Theory] + [InlineData("x^3*asech(a*x)^2")] + [InlineData("x*(a+b*asech(c*x))^2")] + [InlineData("x^3*(a+b*acsch(c*x))^2")] + public void LeavesTheQuotientsByPartsAsksAsWritten(string integrand) + => Assert.DoesNotContain("integral(", integrand.ToEntity().Integrate("x").Stringize()); + } +}