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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6216,6 +6216,24 @@ with `K = sqrt(L1) sqrt(L2)/sqrt(M)` in front of the answer, constant wherever i

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

### Half-odd powers of the two linears whose product is the radicand `arsinh` holds are paired to leave a polynomial

**Answers where there were none.** `(a + b arsinh(c x)) sqrt(f - i c f x)/(d + i c d x)^(3/2)`,
`(d + i c d x)^(3/2) (f - i c f x)^(5/2) (a + b arsinh(c x))` and ten more of Rubi's 7.1.4 were declined. With both
linears above the bar the smaller power is now the one paired into the radicand `M = 1 + c^2 x^2`, so that the whole
power left is above the bar too: `(d + i c d x)^(3/2) (f - i c f x)^(5/2)` is `K^3 (f - i c f x) M^(3/2)`, a polynomial
beside the root. With the second below the bar under a power of `-3/2` or less, the whole power of the first is
written out as monomials, each over the root, and each `x^k (a + b arsinh(c x))/M^(3/2)` is answered alone. Over
`sqrt(M)`, and with both linears below the bar, the pairing is as before
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a+b*asinh(c*x))*sqrt(f-i*c*f*x)/(d+i*c*d*x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,001 characters |
| `"(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 7,148 characters |
| `"(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))".ToEntity().Integrate("x")` | `integral(...)` | 3,624 characters |
| `"(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 7,625 characters |

### The radicand an inverse sine or cosine holds is read as one

`(d + c d x)^(1/2) (f - c f x)^(3/2) (a + b arcsin(c x))` ran out of time. `arcsin(u)` holds a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31898,6 +31898,26 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
/// 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>
/// <remarks>
/// <para>
/// Both factors above the bar, `q` is the smaller power, so that the whole power left is
/// above the bar too: `(d + i c d x)^(3/2) (f - i c f x)^(5/2)` is `K^3 (f - i c f x) M^(3/2)`,
/// a polynomial beside the root. Paired the other way it was `K^5 M^(5/2)/(d + i c d x)`,
/// which ran past the budget, as did the same with `(a + b arsinh(c x))^2`; the polynomial
/// takes half a second.
/// </para>
/// <para>
/// The first factor above the bar and the second below it under a power of `-3/2` or
/// less, the whole power `L1^n` is written out as a sum of monomials, each over the root:
/// `sqrt(f - i c f x)/(d + i c d x)^(3/2)` is `K^(-3) (f - i c f x)^2/M^(3/2)`, and as that
/// product the rules for a power of a quadratic do not read it -- ten of Rubi's 7.1.4
/// rows, from `(f - i c f x)^2` to `(f - i c f x)^5` over `M^(3/2)` and `M^(5/2)`, ran past
/// the budget -- while each `x^k (a + b arsinh(c x))/M^(3/2)` is answered in a tenth of a
/// second. Over `sqrt(M)` the product is answered as it is, and is left so; and both
/// factors below the bar are left as they are paired.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </para>
/// </remarks>
private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Variable x, List<Entity> references,
Entity.Variable multiple, out Entity rewritten, out Entity? root)
{
Expand Down Expand Up @@ -31928,6 +31948,11 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
var twice = (2 * half).Evaled;
if (half is not Number.Integer && twice is not Number.Integer)
continue;
// Both above the bar, the smaller power is the one paired, so that the whole
// power left over is above it too. See the remarks.
var whole = (Number.Integer)(p - q).Evaled;
if (half is not Number.Integer && !half.IsNegative && whole.IsNegative)
continue;
var product = (first * second).Expand();
foreach (var candidate in references)
{
Expand All @@ -31944,6 +31969,14 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
together = MathS.Pow(multiple, twice) * MathS.Pow(candidate, half);
root = MathS.Sqrt(first) * MathS.Sqrt(second) / MathS.Sqrt(candidate);
}
// The first factor above the bar and the root of the radicand 3/2 or more
// below it: a sum of monomials over that root. See the remarks.
if (half is not Number.Integer && half < Number.Rational.Create(-1, 1) && p.Evaled is Number.Real { IsPositive: true }
&& AsMonomialsOverARoot(first, whole, x, factors.Where((_, k) => k != i && k != j), candidate, (Number.Rational)(-half)) is { } monomials)
{
rewritten = MathS.Pow(multiple, twice) * monomials;
return true;
}
Entity result = MathS.Pow(first, (p - q).InnerSimplified) * together;
for (var k = 0; k < factors.Count; k++)
if (k != i && k != j)
Expand All @@ -31961,6 +31994,44 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
Powf(var @base, var exponent) => (@base, exponent),
_ => (factor, Number.Integer.One),
};

// `L^n g/M^r` as `sum_k a_k x^k g/M^r`, each term spelled as a quotient, which is how
// the rules for a power of a quadratic read it; null where L^n is no polynomial.
static Entity? AsMonomialsOverARoot(Entity linear, Number.Integer power, Entity.Variable x,
IEnumerable<(Entity Base, Entity Exponent)> rest, Entity radicand, Number.Rational rootPower)
{
if (!TreeAnalyzer.TryGetPolynomial(MathS.Pow(linear, power), x, out var terms)
|| terms.Keys.Any(key => key.Sign < 0) || terms.Values.Any(coefficient => coefficient.ContainsNode(x)))
return null;
// The other factors, a negative power of one below the bar with the radicand's root.
Entity? beside = null;
Entity under = MathS.Pow(radicand, rootPower);
foreach (var (@base, exponent) in rest)
{
if (exponent.Evaled is Number.Real { IsNegative: true } negative)
{
var positive = (-negative).Evaled;
under = under * (positive == Number.Integer.One ? @base : MathS.Pow(@base, positive));
continue;
}
var factor = exponent == Number.Integer.One ? @base : MathS.Pow(@base, exponent);
beside = beside is null ? factor : beside * factor;
}
Entity? sum = null;
foreach (var pair in terms.OrderBy(term => term.Key))
{
var degree = pair.Key;
var written = pair.Value.InnerSimplified;
if (VanishesIdentically(written))
continue;
Entity term = degree.IsZero ? written : written * (degree.Equals(EInteger.One) ? x : MathS.Pow(x, Number.Integer.Create(degree)));
if (beside is not null)
term = term * beside;
term = term / under;
sum = sum is null ? term : sum + term;
}
return sum;
}
}

/// <summary>Set while <see cref="SolveByWritingEachLinearOnce"/> asks its product.</summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// 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>
/// Two linears whose product is a multiple of the radicand of <c>arsinh(c x)</c>,
/// <c>(d + i c d x)(f - i c f x) = d f (1 + c^2 x^2)</c>, under powers a whole number apart:
/// with the root of the radicand 3/2 or more below the bar the whole power above it is
/// written out as monomials over the root, and with both above the bar the smaller power is
/// paired. Each ran past the budget. Rubi's 7.1.4.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APairOfLinearsOverARadicandAsMonomialsIntegralTest
{
[Theory]
[InlineData("(a+b*asinh(c*x))*sqrt(f-i*c*f*x)/(d+i*c*d*x)^(3/2)")]
[InlineData("(f-i*c*f*x)^(3/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(3/2)")]
[InlineData("(f-i*c*f*x)^(3/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)")]
[InlineData("(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(3/2)")]
[InlineData("(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)")]
[InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(3/2)")]
[InlineData("(d+i*c*d*x)^(3/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(3/2)")]
[InlineData("(a+b*asinh(c*x))*sqrt(d+i*c*d*x)/(f-i*c*f*x)^(3/2)")]
[InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(5/2)")]
[InlineData("(d+i*c*d*x)^(3/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(5/2)")]
[InlineData("(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))")]
[InlineData("(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))^2")]
public void AnsweredAndDifferentiatesBack(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Entity Pinned(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 = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.1, -0.4, 0.3, 0.7, 1.2, 2.1 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading