Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3052,6 +3052,25 @@ polynomial exponent in `u` now, and the question asked in `u`. Rubi's 2.3
| `"F^(a + b/(c + d*x))/(c + d*x)".ToEntity().Integrate("x")` | `integral(...)` | an exponential integral |
| `"F^(a + b/(c + d*x)^3)*(c + d*x)^2".ToEntity().Integrate("x")` | `integral(...)` | an exponential integral of the cube, and the exponential |

### An exponential of the reciprocal of a linear over another linear, and its elementary moments, are integrated under `u = 1/(c + d x)` first

**Answers where there were none, and sooner.** `F^(e + f (a + b x)/(c + d x))/(g + h x)^4` was declined by 2.5.0,
and the unreleased master answered it in ten seconds and 12 GB, the third power in six; `F^(a + b/(c + d x)^3)/(c + d x)^13`,
`^16` and `^19` ran past ten seconds there, Rubi's 2.3. The substitution `u = 1/(c + d x)` that answers each in
hundredths of a second was made only after every other rule had declined. It is made first now where another linear
stands below the bar, and where the integral is elementary: `F^(a + b/(c + d x)^k)/(c + d x)^n` with `n - 1` a positive
multiple of `k >= 2`, a polynomial times an exponential under `w = u^k`. A power of `c + d x` below the bar arrives
written monic, `c/d + x` beside the `c + d x` of the exponent, and is read as a constant multiple of it by value. Two
answers 2.5.0 gave are respelled, `F^(a + b/(c + d x)^2)/(c + d x)^3` and `F^(a + b/(c + d x)^3)/(c + d x)^4`, from 58
characters to 74 ([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 1,800 characters |
| `"F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 969 characters |
| `"F^(a + b/(c + d*x)^3)/(c + d*x)^13".ToEntity().Integrate("x")` | `integral(...)` | 262 characters |
| `"F^(a + b/(c + d*x)^2)/(c + d*x)^3".ToEntity().Integrate("x")` | 58 characters | 74 characters |

### A power of a monomial in a symbolic power of `x` is integrated as the power of `x` it is

**Answers where there were none.** `(c x^n)^b` is written `c^b x^(n b)`, and the exponent `n b` was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26055,6 +26055,113 @@ private static bool TryReadAPowerTimesARadicalQuadratic(Entity expr, Entity.Vari
return (factor * answerInU.Substitute(u, 1 / linear)).InnerSimplified;
}

/// <summary>
/// An exponential of a constant plus a multiple of the reciprocal of a linear over a whole
/// power of another linear, <c>F^(a + b/(c + d x))/(g + h x)^n</c>, under
/// <see cref="SolveAnExponentialInTheReciprocalOfALinear"/>'s substitution <c>u = 1/(c + d x)</c>,
/// asked before the rules that do not read it: in <c>u</c> it is
/// <c>-d^(n - 1) u^(n - 2) F^(a + b u)/((d g - c h) u + h)^n</c>, the exponential integral of a
/// shifted argument, which <see cref="SolveAnExponentialOfALinearOverAPowerOfALinear"/> and
/// <see cref="SolveAnExponentialOverSeveralLinears"/> answer. The exponent may be written
/// <c>e + f (a + b x)/(c + d x)</c>. Rubi's 2.3.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </summary>
/// <remarks>
/// In its place at the end of the dispatch the substitution was reached only after every rule
/// above it had declined, and <c>F^(e + f (a + b x)/(c + d x))/(g + h x)^n</c> took 2 to 10
/// seconds and 0.4 to 12 GB for <c>n = 1</c> to 4, past the budget for 3 and 4, for the answer
/// it writes in a twentieth of a second. Only for an exponent linear in <c>u</c> and another
/// linear below the bar whose slope in <c>u</c>, <c>d g - c h</c>, is not zero as a value. A
/// multiple of <c>c + d x</c> is not another linear, though a power of <c>c + d x</c> alone
/// arrives as one, written monic: its elementary cases are the ansatz's, which writes them
/// shorter, and <see cref="SolveAnElementaryMomentOfAnExponentialInAPowerOfTheReciprocal"/>'s.
/// Another linear above the bar only, and a Gaussian in <c>u</c>, keep the rule's place.
/// </remarks>
internal static Entity? SolveAnExponentialInTheReciprocalOfALinearOverAnotherLinear(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (ReadAnExponentialInTheReciprocalOfALinear(expr, x) is not { ExponentInU: var exponentInU, U: var u, Beside: (_, var below, _) }
|| !below.ContainsNode(u)
|| !TreeAnalyzer.TryGetPolyLinear(exponentInU, u, out var rate, out _) || rate is null || VanishesIdentically(rate))
return null;
// Each linear below the bar a linear in u, its slope not zero as a value.
foreach (var factor in Mulf.LinearChildren(below))
{
var linearInU = factor is Powf(var raised, Number.Integer) ? raised : factor;
if (!TreeAnalyzer.TryGetPolyLinear(linearInU, u, out var slopeInU, out _) || slopeInU is null || VanishesIdentically(slopeInU))
return null;
}
return SolveAnExponentialInTheReciprocalOfALinear(expr, x, integrateByParts);
}

/// <summary>
/// An exponential of a constant plus a multiple of a power of the reciprocal of a linear,
/// over a power of the linear, where the integral is elementary:
/// <c>F^(a + b/(c + d x)^k)/(c + d x)^n</c> with <c>k >= 2</c> and <c>n - 1</c> a positive
/// multiple of <c>k</c>. Under <see cref="SolveAnExponentialInTheReciprocalOfALinear"/>'s
/// <c>u = 1/(c + d x)</c> it is <c>-(1/d) u^(n - 2) F^(a + b u^k)</c>, and under <c>w = u^k</c> a
/// polynomial in <c>w</c> times <c>F^(a + b w)</c>, which the question in <c>u</c> is answered by.
/// Rubi's 2.3. https://github.com/asc-community/AngouriMath/issues/233
/// </summary>
/// <remarks>
/// In the substitution's place at the end of the dispatch these were reached after every rule
/// above it had declined: <c>F^(a + b/(c + d x)^2)/(c + d x)^13</c> took 6 seconds and 6 GB,
/// <c>F^(a + b/(c + d x)^3)/(c + d x)^13</c> and higher ran past the budget, and the questions
/// in <c>u</c> take a hundredth of a second. The power below the bar arrives written monic,
/// <c>c/d + x</c> beside the <c>c + d x</c> of the exponent, and is read as a constant multiple
/// of the linear by value. Only where the integral is elementary, and only for a linear other
/// than the variable: asked here for every power, the exponential integral and the error
/// function came out longer or slower than where they are answered now, a linear exponent in
/// <c>u</c>, which the ansatz answers in the base, three times longer, and the moments of
/// <c>f^(a + b/x^2)</c>, answered in a hundredth of a second already, respelled.
/// </remarks>
internal static Entity? SolveAnElementaryMomentOfAnExponentialInAPowerOfTheReciprocal(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (ReadAnExponentialInTheReciprocalOfALinear(expr, x) is not var (constant, @base, exponentInU, u, linear, slope, power, beside)
|| linear == x || power.Sign >= 0
|| !TreeAnalyzer.TryGetPolynomial(exponentInU, u, out var monomials) || monomials.Count > 2)
return null;
// A + B u^k: one power past the constant, its coefficient not zero as a value.
var raised = monomials.Where(pair => pair.Key.Sign != 0).ToList();
if (raised.Count != 1 || monomials.Keys.Any(degree => degree.Sign < 0) || VanishesIdentically(raised[0].Value))
return null;
var k = raised[0].Key;
// u^(n - 2) du with n - 1 a positive multiple of k is a whole power of w = u^k times dw.
var belowMinusOne = power.Negate().Subtract(EInteger.One);
var powerInU = belowMinusOne.Subtract(EInteger.One);
if (k.CompareTo(EInteger.FromInt32(2)) < 0 || k.CompareTo(EInteger.FromInt32(8)) > 0
|| belowMinusOne.Sign <= 0 || !belowMinusOne.Remainder(k).IsZero || powerInU.CompareTo(EInteger.FromInt32(32)) > 0)
return null;
var factor = -constant / slope;
// Another linear only as a multiple of the linear, which is how the power below the bar
// arrives, written monic: `c/d + x` beside the `c + d x` of the exponent is
// `(d^0 c - c) u + 1` in u, a constant whose slope is zero as a value and not as written.
// Its value at u = 0, and d to the power the reader kept apart. Bare, since the `d^0` in
// it is 1 provided d is not 0, which the substitution already assumes.
if (beside is var (above, below, slopePower))
{
if (!IsAConstantInU(above) || !IsAConstantInU(below))
return null;
var ofTheSlope = slopePower - EInteger.One;
var multiple = Functions.PartialFractions.Bare((above.Substitute(u, Number.Integer.Zero) / below.Substitute(u, Number.Integer.Zero)).InnerSimplified);
factor = ofTheSlope.IsZero ? -constant
: ofTheSlope.Sign > 0 ? -constant * MathS.Pow(slope, Number.Integer.Create(ofTheSlope))
: -constant / MathS.Pow(slope, Number.Integer.Create(-ofTheSlope));
if (multiple != Number.Integer.One)
factor *= multiple;
}
var exponential = MathS.Pow(@base, exponentInU);
var question = powerInU.IsZero ? exponential : MathS.Pow(u, Number.Integer.Create(powerInU)) * exponential;
if (Integration.ComputeAsAQuestionOfItsOwn(question, u, integrateByParts) is not { } answerInU
|| answerInU.Nodes.Any(node => node is Integralf || node == MathS.NaN))
return null;
return (factor * answerInU.Substitute(u, 1 / linear)).InnerSimplified;

// A product of whole powers of linears in u, each of slope zero as a value.
bool IsAConstantInU(Entity product) => !product.ContainsNode(u) || Mulf.LinearChildren(product).All(part =>
!part.ContainsNode(u) || TreeAnalyzer.TryGetPolyLinear(part is Powf(var raisedLinear, Number.Integer) ? raisedLinear : part, u, out var slopeInU, out _)
&& (slopeInU is null || VanishesIdentically(slopeInU)));
}

/// <summary>
/// <paramref name="expr"/> read as a constant times <c>F^E</c> times a whole power of a
/// linear <c>L</c>, with every <c>x</c> in <c>E</c> inside a reciprocal power of <c>L</c>:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -841,11 +841,17 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfAnExponentialAsAMultipleOfOne(expr, x, integrateByParts)) is { }) return answer;
// An exponential of a quadratic in 1/L beside a power of L, onto the Gaussian under u = 1/L.
if ((answer = IndefiniteIntegralSolver.SolveAGaussianInAReciprocal(expr, x)) is { }) return answer;
// And of the reciprocal of a linear over another linear, the same substitution's, ahead of
// its place below: `F^(e + f (a + b x)/(c + d x))/(g + h x)^4` ran past the budget there.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialInTheReciprocalOfALinearOverAnotherLinear(expr, x, integrateByParts)) is { }) return answer;
// And a trigonometric function of the reciprocal of a linear beside a power of it, under u = 1/L.
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheReciprocalOfALinear(expr, x, integrateByParts)) is { }) return answer;
// And one of `a + b/x` beside any rational function, under `x = 1/u`:
// `sin(c + d/x)/(e + f x)` is `-sin(c + d u)/(u (e u + f)) du`.
if ((answer = IndefiniteIntegralSolver.SolveByTheReciprocalOfAFunctionOfTheReciprocal(expr, x, integrateByParts)) is { }) return answer;
// And a power of the linear alone beside an exponential of a power of its reciprocal, where
// that is elementary: `F^(a + b/(c + d x)^3)/(c + d x)^13` ran past the budget below.
if ((answer = IndefiniteIntegralSolver.SolveAnElementaryMomentOfAnExponentialInAPowerOfTheReciprocal(expr, x, integrateByParts)) is { }) return answer;
// An exponential of a multiple of a logarithm is a power of the argument, which is
// how every inverse hyperbolic function under an exponential arrives.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingAnExponentialOfALogarithm(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// 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
{
/// <summary>
/// An exponential of a constant plus a multiple of <c>1/L</c>, <c>L = c + d x</c>, over a whole
/// power of another linear <c>M = g + h x</c>, and of a power of <c>1/L</c> over a power of
/// <c>L</c> where that is elementary. Under <c>u = 1/L</c> the first is an exponential of a
/// linear over a power of a linear in <c>u</c>, the exponential integral of a shifted argument,
/// and the second <c>u^(n - 2) F^(a + b u^k)</c> with <c>n - 1</c> a multiple of <c>k</c>. The
/// substitution was reached only after every other rule had declined:
/// <c>F^(e + f (a + b x)/(c + d x))/(g + h x)^n</c> took 4 to 12 GB for <c>n = 2</c> to 4, and
/// <c>F^(a + b/(c + d x)^3)/(c + d x)^13</c> ran past the budget. Rubi's 2.3.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnExponentialInTheReciprocalOfALinearOverAnotherLinearIntegralTest
{
[Theory]
[InlineData("F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)", 1.1)]
[InlineData("F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)^2", 1.1)]
[InlineData("F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)^3", 1.1)]
[InlineData("F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)^4", 1.1)]
[InlineData("F^(m + f*(a + b*x)/(c + d*x))/(g + h*x)^3", -0.9)]
[InlineData("F^(a + b/(c + d*x))/(k + f*x)^3", 1.1)]
[InlineData("e^(k/(c + d*x))/(a + b*x)^3", -0.9)]
[InlineData("f^(c/(a + b*x))/x^3", 1.1)]
[InlineData("F^(a + b/(c + d*x))*(p + q*x)/(g + h*x)^2", 1.1)]
[InlineData("F^(a + b/(c + d*x))/((g + h*x)*(p + q*x))", 1.1)]
[InlineData("e^(1/(1 + x))/(2 + x)^2", 1.1)]
[InlineData("F^(a + b/(c + d*x)^3)/(c + d*x)^13", 1.1)]
[InlineData("F^(a + b/(c + d*x)^3)/(c + d*x)^4", 1.1)]
[InlineData("F^(a + b/(c + d*x)^2)/(c + d*x)^7", 1.1)]
[InlineData("F^(a + b/(c + d*x)^2)/(c + d*x)^3", 1.1)]
[InlineData("e^(k/(1 + 2*x)^2)/(1 + 2*x)^5", -0.9)]
public void DifferentiatesBack(string integrand, double k)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3)
.Substitute("f", 2.3).Substitute("g", 0.6).Substitute("h", 1.9).Substitute("m", 0.5).Substitute("F", 1.7)
.Substitute("p", 0.9).Substitute("q", 0.45).Substitute("k", k);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -0.9, -0.4, 0.3, 0.8, 1.3, 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}");
}

/// <summary>
/// Answered by the substitution before the rules that decline it: in its old place the
/// declines cost 3.9, 7.9 and 11.7 GB for the second, third and fourth power, and 6.2 and
/// 5.6 GB for the moments. Spelled in
/// symbols no other test here uses, since an integral already answered on the thread is
/// read from the cache and allocates nothing.
/// </summary>
[Theory]
[InlineData("G^(n + s*(r + t*x)/(u + v*x))/(w + y*x)^2")]
[InlineData("G^(n + s*(r + t*x)/(u + v*x))/(w + y*x)^3")]
[InlineData("G^(n + s*(r + t*x)/(u + v*x))/(w + y*x)^4")]
[InlineData("G^(n + s/(u + v*x)^2)/(u + v*x)^13")]
[InlineData("G^(n + s/(u + v*x)^3)/(u + v*x)^10")]
public void IsAnsweredBeforeTheSearch(string integrand)
{
var expression = integrand.ToEntity();
var before = GC.GetAllocatedBytesForCurrentThread();
var text = expression.Integrate("x").Stringize();
var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.DoesNotContain("integral(", text);
Assert.True(allocated < 1_000_000_000L, $"integrating {integrand} allocated {allocated} bytes");
}
}
}
Loading