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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,21 @@ is real on `(-1/2, 1/2)`, where `arcosh(2x)` is `i arccos(2x)`.

Rubi's 7.2.4 and 7.2.5, all 277 problems that count: 127 to 255, no row lost, 121 timeouts to 18.

### Two linear factors whose product is a multiple of the radicand `arsinh` holds are written as that radicand

`(a + b arsinh(c x)) sqrt(d + i c d x) sqrt(f - i c f x)` ran out of time. The two linear factors
multiply to `d f (1 + c^2 x^2)`, a constant multiple of the radicand `arsinh(c x) =
ln(c x + sqrt(c^2 x^2 + 1))` holds -- the entry above's case, spelled as two factors. `L1^p L2^q`
with `p - q` whole is now `L1^(p - q) L1^q L2^q`, and `L1^q L2^q` is `K^k M^(k/2)` for `q = k/2`,
with `K = sqrt(L1) sqrt(L2)/sqrt(M)` in front of the answer, constant wherever it is defined.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a+b*asinh(c*x))*sqrt(d+i*c*d*x)*sqrt(f-i*c*f*x)".Integrate("x")` | left unevaluated | in `arsinh(c x)` and `1 + c^2 x^2`, with `K` in front |
| `"(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))*sqrt(f-i*c*f*x)".Integrate("x")` | left unevaluated | the same, with `(d + i c d x)^2` beside it |

Rubi's 7.1.4 and 7.1.5, all 376 problems that count: 308 to 345, no row lost, 56 timeouts to 27.

### `binomial(n, k)` is a function

**Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15018,21 +15018,95 @@ static bool Through(Entity node, Entity.Variable x)
}
return node;
});
if (written is not null && reference is not null)
root = MathS.Sqrt(written) / MathS.Sqrt(reference);
// And the radicand written as two linear factors, when no single base was its multiple.
if (rewritten == expr && TryWriteAPairOfLinearsOverARadicand(expr, x, references, multiple, out var paired, out var pairRoot))
(rewritten, root) = (paired, pairRoot);
if (rewritten == expr)
return null;
var simplified = rewritten.InnerSimplified;
if (simplified == expr || simplified == expr.InnerSimplified)
return null;
if (Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts) is not { } answer)
return null;
if (written is not null && reference is not null)
{
root = MathS.Sqrt(written) / MathS.Sqrt(reference);
if (root is not null)
answer = answer.Substitute(multiple, root);
}
return answer.ContainsNode(multiple) ? null : answer;
}

/// <summary>
/// Two linear factors whose product is a constant multiple of a radicand a logarithm holds,
/// written as that radicand: `d + i c d x` and `f - i c f x` beside `arsinh(c x)`, whose
/// product is `d f (1 + c^2 x^2)`. `L1^p L2^q` is `L1^(p - q) L1^q L2^q` where `p - q` is
/// whole, and `L1^q L2^q` is `lambda^q M^q` for a whole `q` and `K^k M^(k/2)` for
/// `q = k/2`, with `K = sqrt(L1) sqrt(L2)/sqrt(M)` constant wherever it is defined -- the
/// same factor the single base is written with. Rubi's 7.1.4 states seventy-two of its
/// rows this way; `(d + i c d x)^(5/2) sqrt(f - i c f x) (a + b arsinh(c x))` ran past
/// seventy seconds where the same thing over `1 + c^2 x^2` takes two thirds of one.
/// </summary>
private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Variable x, List<Entity> references,
Entity.Variable multiple, out Entity rewritten, out Entity? root)
{
rewritten = expr;
root = null;
// The factors on both sides of the bar, as powers: a nested power read through, and a
// factor below the bar with its exponent negated.
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr));
var factors = new List<(Entity Base, Entity Exponent)>();
foreach (var factor in Mulf.LinearChildren(above))
factors.Add(AsAPower(factor));
foreach (var factor in Mulf.LinearChildren(below))
{
var (@base, exponent) = AsAPower(factor);
factors.Add((@base, (-exponent).InnerSimplified));
}
for (var i = 0; i < factors.Count; i++)
for (var j = 0; j < factors.Count; j++)
{
if (i == j)
continue;
var (first, p) = factors[i];
var (second, q) = factors[j];
if (!first.ContainsNode(x) || !second.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(first, x, out _, out _) || !TreeAnalyzer.TryGetPolyLinear(second, x, out _, out _)
|| (p - q).Evaled is not Number.Integer || q.Evaled is not Number.Rational half)
continue;
var twice = (2 * half).Evaled;
if (half is not Number.Integer && twice is not Number.Integer)
continue;
var product = (first * second).Expand();
foreach (var candidate in references)
{
if (TryReadAsAConstantMultiple(product, candidate, x) is not { } lambda)
continue;
Entity together;
if (half is Number.Integer)
together = MathS.Pow(lambda, half) * MathS.Pow(candidate, half);
else
{
together = MathS.Pow(multiple, twice) * MathS.Pow(candidate, half);
root = MathS.Sqrt(first) * MathS.Sqrt(second) / MathS.Sqrt(candidate);
}
Entity result = MathS.Pow(first, (p - q).InnerSimplified) * together;
for (var k = 0; k < factors.Count; k++)
if (k != i && k != j)
result = result * MathS.Pow(factors[k].Base, factors[k].Exponent);
rewritten = result;
return true;
}
}
return false;

static (Entity Base, Entity Exponent) AsAPower(Entity factor)
=> factor switch
{
Powf(Powf(var inner, var innerExponent), var outerExponent) => (inner, (innerExponent * outerExponent).InnerSimplified),
Powf(var @base, var exponent) => (@base, exponent),
_ => (factor, Number.Integer.One),
};
}

/// <summary>
/// Whether <paramref name="expr"/>/<paramref name="reference"/> takes the same value at two
/// points, every other symbol pinned to a fixed value -- a necessary condition for a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,42 @@ private static void DifferentiatesBack(string integrand, double[] points)
public void AConstantMultipleOfTheRadicandIsWrittenOverIt(string integrand, double[] points)
=> DifferentiatesBack(integrand, points);

/// <summary>
/// The radicand written as two linear factors: <c>(5 + 10 i x)(7 - 14 i x)</c> is
/// <c>35 (1 + 4 x^2)</c>, the radicand of <c>arsinh(2x)</c>, so <c>L1^p L2^q</c> is
/// <c>L1^(p - q) K^k (1 + 4 x^2)^(k/2)</c> with <c>K = sqrt(L1) sqrt(L2)/sqrt(1 + 4 x^2)</c>
/// in front, constant wherever it is defined. Rubi's 7.1.4 and 7.1.5, 39 rows; the
/// integrands are complex everywhere, and compared so.
/// </summary>
[Theory]
[InlineData("(3 + 2*arsinh(2*x))*sqrt(5 + 10*i*x)*sqrt(7 - 14*i*x)", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })]
[InlineData("(5 + 10*i*x)^(5/2)*(3 + 2*arsinh(2*x))*sqrt(7 - 14*i*x)", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })]
[InlineData("(5 + 10*i*x)^(3/2)*(7 - 14*i*x)^(3/2)*(3 + 2*arsinh(2*x))", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })]
[InlineData("x*(3 + 2*arsinh(2*x))*sqrt(5 + 10*i*x)*sqrt(7 - 14*i*x)", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })]
public void ARadicandWrittenAsTwoLinearFactorsIsWrittenAsOne(string integrand, double[] points)
=> DifferentiatesBack(integrand, points);

/// <summary>The same with every coefficient a symbol, pinned only after integrating.</summary>
[Theory]
[InlineData("(a+b*asinh(c*x))*sqrt(d+i*c*d*x)*sqrt(f-i*c*f*x)")]
[InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))*sqrt(f-i*c*f*x)")]
public void TwoLinearFactorsWithSymbolicCoefficients(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 0.9).Substitute("b", 1.7).Substitute("c", 0.6).Substitute("d", -1.3).Substitute("f", 0.8);
var derivative = Pin(integral.Substitute("C", 0)).Differentiate("x");
var original = Pin(integrand.ToEntity());
foreach (var at in new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-9, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

/// <summary>
/// A root of a *negative* multiple of the quadratic is not the multiple's root times
/// the quadratic's: <c>arcosh(a x)^2/sqrt(1 - a^2 x^2)</c> came back with <c>i a</c>
Expand Down
Loading