diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 7b8cb4a18..f272ff529 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -2278,6 +2278,26 @@ takes the branches of that respelling
| `"sec(x)*sqrt(cos(x) - sin(x))/sin(x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 74 characters |
| `"1/(cos(x)^(3/2)*sqrt(3*cos(x) + sin(x)))".ToEntity().Integrate("x")` | `integral(...)` | `2 * sqrt(tan(x) + 3)` |
+### A substitution's quotient that still varies with x is not simplified
+
+**Answers where there were none.** `tan(c + d x)^(11/2)/(a + b tan(c + d x))^3` ran out of time, and six more of
+Rubi's logarithmic, trigonometric and hyperbolic integrands did on the unreleased master. The time went in simplifications
+deep in the substitution search, of a candidate's quotient with symbols in it, where that quotient in `u` and `x`,
+the two read as independent, still varies with `x`: a rewriting that keeps its value cannot take that `x` out. Two
+values at two positive points now say so, and the simplification is not tried; the same in the step that writes the
+integrand as a polynomial in the candidate, in the pass in the complement, and in the scaling of the variable, which
+reads at two points whether the integrand is homogeneous first. A quotient that does not vary is simplified as before,
+and so is one whose candidate has a symbolic factor free of `x` and may leave a power of itself, whose simplification is
+what carries the substitution's own condition, `provided not k = 0` beside `F^(k sqrt(1 - a x)/sqrt(1 + a x))`
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"tan(c+d*x)^(11/2)/(a+b*tan(c+d*x))^3".ToEntity().Integrate("x")` | no answer in twenty seconds | 4,909 characters |
+| `"tan(c+d*x)^4/(a+i*a*tan(c+d*x))^(4/3)".ToEntity().Integrate("x")` | `integral(...)` | 3,713 characters |
+| `"1/((c+d*sin(k+f*x))*sqrt(g*sin(k+f*x))*sqrt(a+a*sin(k+f*x)))".ToEntity().Integrate("x")` | `integral(...)` | 1,473 characters |
+| `"x^(7/2)/((a+b*x^2)^2*(c+d*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 2,685 characters |
+
### An even rational function with symbols in it is not searched for a substitution
**Answers where there were none.** `(d + e x^2 + f x^4)/(x^4 (a + b x^2 + c x^4)^2)` ran past a minute: the
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 5a953b156..6c6c575e1 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -33818,6 +33818,16 @@ static System.Numerics.Complex ToComplex(Number.Complex number)
// dx = c dt.
var t = Variable.CreateUnique(expr, "u_scale");
+ // Homogeneous or not, read at two points first: the scaled integrand over itself at
+ // c = 1 is c^k wherever it is c^k h(t), the same at every t for a positive c, and
+ // where it is not the same the simplifications below cannot make it so. Each of
+ // them is a simplification of the whole integrand with the symbol in it, and the
+ // third alone, of the quotient, ran past thirty seconds on the rational function the
+ // imaginary tangent's substitution makes of Rubi's `tan(c + d x)^4/(a + i a tan(c + d x))^(4/3)`
+ // before it was declined. https://github.com/asc-community/AngouriMath/issues/718
+ var scaledAsWritten = expr.Substitute(x, scale * t) * scale;
+ if (VariesWith((scaledAsWritten / scaledAsWritten.Substitute(scale, Number.Integer.One)).Substitute(scale, Number.Rational.Create(137, 100)), t))
+ return null;
// With the imaginary unit in the integrand the simplifier's search beside the symbol
// does not end within any budget, and inner simplification separates what a whole
// power of the scale does: `((1 + i a x)/(1 - i a x))^(-5/4)/x^2` under its radical's
@@ -42196,6 +42206,83 @@ void Add(Entity root, Entity times)
}
}
+ ///
+ /// Whether takes two clearly different values at two positive
+ /// points of , every other symbol pinned to the same number at both:
+ /// then it depends on as a function of all its symbols, and no
+ /// rewriting that keeps its value writes it without . False wherever
+ /// a value cannot be had, and wherever the two agree.
+ ///
+ private static bool VariesWith(Entity expr, Entity.Variable x) => TakesTwoValues(expr, x) == true;
+
+ ///
+ /// , with where a value cannot be had at
+ /// either point: false is a decision that the two agree.
+ ///
+ private static bool? TakesTwoValues(Entity expr, Entity.Variable x)
+ {
+ var values = new[] { "1.37", "0.61", "2.23", "1.91", "0.83", "1.13" };
+ var pinned = expr;
+ var index = 0;
+ foreach (var symbol in expr.Vars.Distinct().ToList())
+ if (symbol != x)
+ pinned = pinned.Substitute(symbol, Number.Real.Create(EDecimal.FromString(values[index++ % values.Length])));
+ Number.Complex? At(string point)
+ => pinned.Substitute(x, Number.Real.Create(EDecimal.FromString(point))).Evaled is Number.Complex { IsNaN: false } number && number.IsFinite
+ ? number : null;
+ if (At("0.47") is not { } first || At("1.53") is not { } second)
+ return null;
+ var scale = System.Math.Max(1.0, System.Math.Max(((Number.Real)first.Abs()).EDecimal.ToDouble(), ((Number.Real)second.Abs()).EDecimal.ToDouble()));
+ return ((Number.Real)(first - second).Abs()).EDecimal.ToDouble() > 1e-6 * scale;
+ }
+
+ ///
+ /// Whether is a factor free of with a
+ /// symbol in it times a function of , and what it leaves of
+ /// in may be a constant times its power
+ /// from -2 to 2: the quotient by one of those powers does not vary with ,
+ /// read off two points, or cannot be read there. That is what
+ /// takes from the simplified quotient, where the
+ /// factor stands on both sides of the bar and its cancelling is the condition that it is not
+ /// zero, under which the candidate varies at all. Never beside a linear candidate, which that
+ /// rewriting does not read.
+ ///
+ private static bool APowerOfACandidateWithASymbolicFactorMayBeLeftIn(Entity integrandInU, Entity candidate, Entity.Variable x)
+ {
+ if (IsALinearCandidate(candidate, x))
+ return false;
+ var (above, below) = Functions.SingleQuotient.Of(candidate);
+ return Mulf.LinearChildren(above).Concat(Mulf.LinearChildren(below)).Any(factor => !factor.ContainsNode(x) && factor.Vars.Any())
+ && APowerOfTheCandidateMayBeLeftIn(integrandInU, candidate, x);
+ }
+
+ /// Whether is written as a polynomial of degree at most one in .
+ private static bool IsALinearCandidate(Entity candidate, Entity.Variable x)
+ => IsWrittenAsAPolynomial(candidate, x) && TreeAnalyzer.TryGetPolynomial(candidate, x, out var asAPolynomial)
+ && asAPolynomial.Keys.Max()!.CompareTo(EInteger.One) <= 0;
+
+ ///
+ /// Whether what leaves of may be a
+ /// constant times a power of from -2 to 2, the powers
+ /// reads: the quotient by one of them does not
+ /// vary with , read off two points, or cannot be read there.
+ ///
+ private static bool APowerOfTheCandidateMayBeLeftIn(Entity integrandInU, Entity candidate, Entity.Variable x)
+ => APowerOfTheCandidateIsLeftIn(integrandInU, candidate, x, unreadIsLeft: true);
+
+ ///
+ /// Whether the quotient of by a power of
+ /// from -2 to 2 takes one value at two points: or may, where
+ /// , for a quotient that cannot be read there.
+ ///
+ private static bool APowerOfTheCandidateIsLeftIn(Entity integrandInU, Entity candidate, Entity.Variable x, bool unreadIsLeft)
+ {
+ foreach (var power in new[] { -1, 1, -2, 2 })
+ if (TakesTwoValues(integrandInU / MathS.Pow(candidate, power), x) is var takes && (takes is false || unreadIsLeft && takes is null))
+ return true;
+ return false;
+ }
+
///
/// Whether over takes two values at
/// two points where takes one -- every other symbol pinned --
@@ -42591,6 +42678,10 @@ private static bool VariesWithTheVariableWhereTheCandidateIsHeld(Entity expr, Va
///
private static Entity? WithTheCandidatesPowerLeftInX(Entity integrandInU, Entity u, Variable uSub, Entity.Variable x)
{
+ // A condition on the constants, `provided not e = 0` from a change of scale, is read
+ // past and kept: as one factor it held both x and the candidate's variable.
+ if (integrandInU is Providedf(var provided, var condition) && !condition.ContainsNode(x) && !condition.ContainsNode(uSub))
+ return WithTheCandidatesPowerLeftInX(provided, u, uSub, x) is { } read ? read.Provided(condition) : null;
// Not for a linear candidate: what is left of x beside `u = f x` is always a power of u,
// and taking the change of scale here asks the same question again in u, ahead of the
// rules that answer it. `(c + d x)^2/(a + i a tan(e + f x))^3` is answered in 33 s,
@@ -45968,13 +46059,19 @@ private static bool HoldsTheImaginaryUnit(Entity expr)
///
private static Entity SimplifiedWithoutTheImaginaryUnit(Entity written, Entity integrand)
{
- var simplified = written.Simplify(1);
- if (simplified is Providedf(var inner, _)) simplified = inner; // TODO: singularities ignored but not handled properly
+ var simplified = WithoutItsCondition(written.Simplify(1));
return HoldsTheImaginaryUnit(simplified) && !HoldsTheImaginaryUnit(written) && !HoldsTheImaginaryUnit(integrand)
? written
: simplified;
}
+ ///
+ /// without a condition written over the whole of it, which the
+ /// integral in a candidate's variable does not read.
+ ///
+ private static Entity WithoutItsCondition(Entity integrandInU)
+ => integrandInU is Providedf(var inner, _) ? inner : integrandInU; // TODO: singularities ignored but not handled properly
+
///
/// Attempts to solve an integral using u-substitution.
/// Looks for patterns where f(g(x)) * g'(x) can be integrated as F(g(x)).
@@ -46254,6 +46351,10 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
var complemented = WithTheComplementInEvenPowers(firstPass[u], u, uSub);
if (complemented == firstPass[u])
continue; // nothing the first pass did not see
+ // Nothing after the simplification here reads the candidate, so an x the quotient
+ // varies with, which the simplification cannot take out, refuses it at once.
+ if (VariesWith(complemented, x))
+ continue;
integrandInU = SimplifiedWithoutTheImaginaryUnit(complemented, expr);
}
else
@@ -46324,7 +46425,40 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
&& !inTermsOfU.Nodes.Any(node => IsASpecialFunction(node) && node.ContainsNode(x))
&& VariesWhereASpecialFunctionIsHeld(inTermsOfU, uSub, x))
continue;
- integrandInU = !inTermsOfU.ContainsNode(x)
+ // An x left that the quotient in u and x, the two read as independent, varies
+ // with: the simplification, the cancelling and the gathering below keep the
+ // value, and none of them can take that x out, so none is tried. Each was a
+ // simplification of a rational function with symbols in it to be told so, and
+ // one alone can run past any budget: the remainder the division leaves of
+ // `t^(11/2)/((a/b + t)^3 (1 + t^2))`, which the tangent makes of Rubi's
+ // `tan(c + d x)^(11/2)/(a + b tan(c + d x))^3`, took thirty-one seconds to be
+ // simplified under `u = t^(3/2)` and forty-four for the four powers of t before
+ // `u = sqrt(t)` answered it. The rewritings after them, which read u = g(x),
+ // still see the quotient. Not under a sine or a cosine, whose quotient the pass
+ // in the complement reads simplified.
+ // And not where the candidate has a symbolic factor free of x and what is left
+ // in x may be a constant times a power of it, which the rewriting below reads off
+ // the simplified quotient: under `u = k sqrt(1 - a x)/sqrt(1 + a x)` Rubi's
+ // `F^u/(1 - a^2 x^2)` over du/dx is `-F^u/(a u)`, and read off the simplified
+ // quotient it keeps a `k/k` whose cancelling is the answer's `provided not k = 0`,
+ // where the candidate varies at all. Read off the quotient as written, the answer
+ // came back without it. Beside a candidate with no such factor the rewriting
+ // reads either spelling alike, and the simplification is still not tried.
+ // https://github.com/asc-community/AngouriMath/issues/718
+ var keepsTheVariable = u is not (Sinf or Cosf) && inTermsOfU.ContainsNode(x) && VariesWith(inTermsOfU, x)
+ && !APowerOfACandidateWithASymbolicFactorMayBeLeftIn(inTermsOfU, u, x);
+ // Kept, its sums in x alone have their like terms collected, which the inner
+ // simplification leaves standing: under `u = t^2`, what Rubi's
+ // `1/((d + e x) (a + b (d + e x)^2 + c (d + e x)^4)^3)` leaves in `t = d + e x` is
+ // `1/((d + t - d) 2 t (a + b u + c u^2)^3)`, whose `t^2` is the candidate, and read
+ // with the `d` in it, it was refused as a power of nothing. And a condition on it is
+ // taken off, as the simplification below takes its own: the shifted linear writes
+ // `c + d^0 (t - c)`, inner-simplified to `1 provided not d = 0`, and under `u = t^3`
+ // Rubi's `1/((c + d x) (a + b (c + d x)^3))` left `1/(3 u (a + b u)) provided not d = 0`,
+ // whose integral in u was declined.
+ integrandInU = keepsTheVariable
+ ? WithoutItsCondition(inTermsOfU.InnerSimplified).Replace(node => node is Sumf or Minusf && node.ContainsNode(x) && !node.ContainsNode(uSub) ? Collected(node) : node)
+ : !inTermsOfU.ContainsNode(x)
&& TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out var offset) && !slope.ContainsNode(x) && !offset.ContainsNode(x)
&& IsAFunctionOfTheTangentAlone(inTermsOfU, uSub) && !HoldsTheImaginaryUnit(inTermsOfU)
? Functions.PartialFractions.Bare(inTermsOfU.InnerSimplified)
@@ -46339,7 +46473,7 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
// A factor written on both sides of the bar cancelled, where x survived:
// the one-level simplification leaves `u/((a w + b)^2 p u)` as it is, and
// the candidate was refused for the u it did not cancel.
- if (integrandInU.ContainsNode(x))
+ if (!keepsTheVariable && integrandInU.ContainsNode(x))
{
var (top, bottom) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(integrandInU));
var cancelled = CancelCommonFactors(top, bottom);
@@ -46368,7 +46502,7 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
// from: done for every candidate that left an x, it gathered the exponentials of
// `1/((c + d x)^3 (a + a tanh(e + f x)))` and simplified them for every one, and
// a decline in a third of a second became a timeout.
- if (integrandInU.ContainsNode(x) && IsASpecialFunction(u))
+ if (!keepsTheVariable && integrandInU.ContainsNode(x) && IsASpecialFunction(u))
{
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(integrandInU));
var spread = above;
@@ -46399,7 +46533,14 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
// linears on either side of a nested division.
var (aboveTheBar, belowTheBar) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(integrandInU));
var inTheCandidate = WithPolynomialsInTheCandidate(aboveTheBar, u, uSub, x) / WithPolynomialsInTheCandidate(belowTheBar, u, uSub, x);
- if (inTheCandidate.ContainsNode(x) == false || inTheCandidate != aboveTheBar / belowTheBar)
+ // And not simplified where what is left still varies with x: `t` below the
+ // bar beside `u = 1 - t^2` is no polynomial in it, and the simplification of
+ // `u^(9/2)/(2 t (c u + d (2 - u))^2 (2 - u))`, which the half-angle tangent
+ // leaves of Rubi's `1/((a + a sec(e + f x))^(5/2) (c + d sec(e + f x))^2)`,
+ // did not return in five minutes.
+ if (inTheCandidate.ContainsNode(x) && VariesWith(inTheCandidate, x) && !APowerOfACandidateWithASymbolicFactorMayBeLeftIn(inTheCandidate, u, x))
+ integrandInU = WithoutItsCondition(inTheCandidate.InnerSimplified);
+ else if (inTheCandidate.ContainsNode(x) == false || inTheCandidate != aboveTheBar / belowTheBar)
{
integrandInU = SimplifiedWithoutTheImaginaryUnit(inTheCandidate, expr);
}
@@ -46412,6 +46553,23 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
// https://github.com/asc-community/AngouriMath/issues/718
if (integrandInU.ContainsNode(x) && WithTheCandidatesPowerLeftInX(integrandInU, u, uSub, x) is { } inPowersOfTheCandidate)
integrandInU = inPowersOfTheCandidate;
+ // Kept, and an x still left that is a power of the candidate, which the kept
+ // spelling did not let the rewriting read: simplified after all, as it was before
+ // the gate, and read again. Beside `u = x^x` the kept quotient of Rubi's
+ // `e^(x^x) x^(2 x) (1 + ln(x))` is `e^u x^(2 x) (1 + ln(x))/(u (x/x + ln(x)))`,
+ // whose `x/x` the inner simplification leaves standing, and the `u^2` in it was
+ // not seen. Only where the quotient by the power is read and is one value, and not
+ // beside a special function of x, whose value in decimals is a series: read four
+ // times for every candidate kept, `erf(b x)` took Rubi's `x^5 erf(b x)^2` to 4.85 GB
+ // where it takes 1.63.
+ if (keepsTheVariable && integrandInU.ContainsNode(x) && !IsALinearCandidate(u, x)
+ && !inTermsOfU.Nodes.Any(node => IsASpecialFunction(node) && node.ContainsNode(x))
+ && APowerOfTheCandidateIsLeftIn(inTermsOfU, u, x, unreadIsLeft: false))
+ {
+ integrandInU = SimplifiedWithoutTheImaginaryUnit(inTermsOfU, expr);
+ if (integrandInU.ContainsNode(x) && WithTheCandidatesPowerLeftInX(integrandInU, u, uSub, x) is { } readAgain)
+ integrandInU = readAgain;
+ }
if (u is Sinf or Cosf && integrandInU.ContainsNode(x))
firstPass[u] = integrandInU;
}
diff --git a/Sources/Tests/UnitTests/Calculus/ASubstitutionThatKeepsTheVariableIsNotSimplifiedIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ASubstitutionThatKeepsTheVariableIsNotSimplifiedIntegralTest.cs
new file mode 100644
index 000000000..ebaffca27
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ASubstitutionThatKeepsTheVariableIsNotSimplifiedIntegralTest.cs
@@ -0,0 +1,111 @@
+//
+// 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 System.Threading;
+using System.Threading.Tasks;
+using AngouriMath.Extensions;
+using Xunit;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// Rubi's rows that ran past the corpus's budget beneath the polynomial term, each of them in
+ /// one simplification of a rational function with symbols in it: a substitution candidate's
+ /// quotient that still varied with x, or a scaled integrand that was not homogeneous. Neither
+ /// can lose its x by a rewriting that keeps the value, and the search now reads that off two
+ /// points instead. tan(c + d x)^(11/2)/(a + b tan(c + d x))^3 took thirty-five seconds
+ /// and the other two did not return in two minutes.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ASubstitutionThatKeepsTheVariableIsNotSimplifiedIntegralTest
+ {
+ private static Entity IntegrateWithinTheGuard(string integrand)
+ {
+ Entity? answer = null;
+ // A thread of its own, so that the guard is not spent waiting behind other tests' work,
+ // and a wall clock that only a search which does not end can reach.
+ var task = Task.Factory.StartNew(() => answer = integrand.ToEntity().Integrate("x"),
+ CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+ Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}");
+ return answer!;
+ }
+
+ [Theory]
+ // Rubi 4.3.2.1:690, under the tangent and then the root of it.
+ [InlineData("tan(c + d*x)^(11/2)/(a + b*tan(c + d*x))^3", new[] { 0.1, 0.3, 0.5, 0.7, 0.9, 1.1 })]
+ // Rubi 4.3.2.1:349, in the imaginary sum, which the scaling read twice before declining.
+ [InlineData("tan(c + d*x)^4/(a + i*a*tan(c + d*x))^(4/3)", new[] { -0.9, -0.4, 0.2, 0.6, 1.1, 1.7 })]
+ // Rubi 4.1.2.3:77, under the half angle, where the sine is positive.
+ [InlineData("1/((c + d*sin(k + f*x))*sqrt(g*sin(k + f*x))*sqrt(a + a*sin(k + f*x)))", new[] { 0.1, 0.3, 0.5, 0.7, 0.9, 1.1 })]
+ // Rubi 1.2.3.2:854, in the shifted linear and then its square: what is left there is the
+ // square written with the shift in it, which is the candidate once its terms are collected.
+ [InlineData("1/((d + g*x)*(a + b*(d + g*x)^2 + c*(d + g*x)^4)^3)", new[] { 0.1, 0.3, 0.5, 0.7, 0.9, 1.1 })]
+ // Rubi 1.1.3.2:3483, in the shifted linear and then its cube: the shift's `d^0` is
+ // `1 provided not d = 0` inner-simplified, and the condition comes off what is kept.
+ [InlineData("1/((c + d*x)*(a + b*(c + d*x)^3))", new[] { 0.1, 0.3, 0.5, 0.7, 0.9, 1.1 })]
+ // Rubi 2.3:805, under `u = x^x`, whose kept quotient holds an `x/x` the inner simplification
+ // leaves: what is left is a power of the candidate, and so it is simplified after all.
+ [InlineData("e^(x^x)*x^(2*x)*(1 + ln(x))", new[] { 0.1, 0.3, 0.5, 0.7, 0.9, 1.1 })]
+ public void IsAnswered(string integrand, double[] points)
+ {
+ var integral = IntegrateWithinTheGuard(integrand);
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4)
+ .Substitute("d", 0.9).Substitute("f", 0.8).Substitute("g", 1.7).Substitute("k", 0.2);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in points)
+ {
+ 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}");
+ }
+
+ ///
+ /// Rubi 2.3's F^(k sqrt(1 - a x)/sqrt(1 + a x))/(1 - a^2 x^2), under the exponent: what
+ /// it leaves in x is a power of the candidate, read off the simplified quotient, and so that
+ /// one is still simplified. The answer, -Ei(ln(F) u)/a, has no value at k = 0,
+ /// where the candidate is constant and the integrand is 1/(1 - a^2 x^2), and it says so.
+ ///
+ [Fact]
+ public void KeepsTheConditionWhereTheCandidateVaries()
+ {
+ const string integrand = "F^(k*sqrt(1 - a*x)/sqrt(1 + a*x))/(1 - a^2*x^2)";
+ var integral = IntegrateWithinTheGuard(integrand);
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.Contains("provided not k = 0", text);
+ Entity Pinned(Entity e) => e.Substitute("F", 1.7).Substitute("k", 0.6).Substitute("a", 1.3);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -0.6, -0.4, -0.2, 0.1, 0.3, 0.5 })
+ {
+ 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}");
+ }
+ }
+}