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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,23 @@ case is most of its length ([#718](https://github.com/asc-community/AngouriMath/
| `"x^4/((a + b*x^2 + c*x^4)*sqrt(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 28,551 characters |
| `"1/(x^2*(a + b*x^2 + c*x^4)*sqrt(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 10,194 characters |

### Two linears to one power are integrated as one quadratic to that power

**Answers where there were none.** `(2 + 3 x)^4/((1 - 2 x)^(5/2) (3 + 5 x)^(5/2))` ran past any budget, and
`1/((1 - x)^(1/3) (2 - x)^(1/3) x)` was declined. `L1^p L2^p` for linears with numbers for coefficients and `p` not
whole, other than `1/2`, is now integrated as `(L1^p L2^p/Q^p)` times the integral over `Q^p`, `Q = L1 L2`. That
quotient is constant on each interval between the roots, so it stays in the answer as written
([#718](https://github.com/asc-community/AngouriMath/issues/718)). `e` is Euler's number below.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/((1-x)^(1/3)*(2-x)^(1/3)*x)".ToEntity().Integrate("x")` | `integral(...)` | 381 characters |
| `"(e*x)^(3/2)/((1-x)^(1/4)*(1+x)^(1/4))".ToEntity().Integrate("x")` | `integral(...)` | 1,403 characters |
| `"1/((1-x)^(1/4)*(e*x)^(1/2)*(1+x)^(1/4))".ToEntity().Integrate("x")` | `integral(...)` | 1,226 characters |
| `"(2+3*x)^6/((1-2*x)^(5/2)*(3+5*x)^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | 1,236 characters |
| `"(2+3*x)^4/((1-2*x)^(5/2)*(3+5*x)^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | 784 characters |
| `"1/((1-2*x)^(5/2)*(2+3*x)^2*(3+5*x)^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | 382 characters |

### A power of an even quadratic over a binomial in it is split over the binomial's roots

**Answers where there were none.** `(1 - x^2)/(a + b (1 - x^2)^4)` was declined. `w^m/(a + b w^n)` for `w = p + q x^2` and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19792,6 +19792,76 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
}

/// <summary>
/// Two linears to one power that is not whole, as one quadratic:
/// <c>integral g L1^p L2^p = (L1^p L2^p/Q^p) integral g Q^p</c> for <c>Q = L1 L2</c>.
/// </summary>
/// <remarks>
/// <c>L1^p L2^p/Q^p</c> is constant wherever it is continuous -- it is 1 where both linears
/// are positive and a fixed root of unity on each interval between their roots -- so its
/// derivative is zero and the product differentiates back. Rubi's 1.1.1.3
/// <c>(e x)^(3/2)/((1 - x)^(1/4) (1 + x)^(1/4))</c> was declined, and
/// <c>(2 + 3 x)^4/((1 - 2 x)^(5/2) (3 + 5 x)^(5/2))</c> took forty-five seconds; over
/// <c>(1 - x^2)^(1/4)</c> and <c>(3 - x - 10 x^2)^(5/2)</c> they take half a second.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveTwoLinearsToOnePowerAsOneQuadratic(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
// Without a leading `1 *`, which is another spelling to the rules that read the result.
static Entity Times(Entity a, Entity b) => a == Number.Integer.One ? b : a * b;
static Entity Over(Entity a, Entity b) => a / b;
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
var factors = new List<(Entity Base, Number.Rational Power, bool Below, Entity Factor)>();
Entity rest = Number.Integer.One;
foreach (var (side, below) in new[] { (numerator, false), (denominator, true) })
foreach (var factor in Mulf.LinearChildren(side))
{
if (factor is Powf(var @base, Number.Rational power) && power is not Number.Integer
&& TreeAnalyzer.TryGetPolyLinear(@base, x, out var slope, out _) && !VanishesIdentically(slope))
factors.Add((@base, power, below, factor));
else
rest = below ? Over(rest, factor) : Times(rest, factor);
}
Number.Rational Signed((Entity Base, Number.Rational Power, bool Below, Entity Factor) f) => f.Below ? (Number.Rational)(-f.Power) : f.Power;
for (var i = 0; i < factors.Count; i++)
for (var j = i + 1; j < factors.Count; j++)
{
var (first, second) = (factors[i], factors[j]);
var power = Signed(first);
if (power != Signed(second)
// Square roots of two linears belong to the rules for Rubi's 1.1.1.6 and
// to the combined radicals, which write the sign as sgn; and with symbols in
// the linears the rules for each of them answer, in a tenth of the time.
|| power == Number.Rational.Create(1, 2) || power == Number.Rational.Create(-1, 2)
|| (first.Base * second.Base).Vars.Any(symbol => symbol != x)
|| !TreeAnalyzer.TryGetPolyLinear(first.Base, x, out var s1, out var c1)
|| !TreeAnalyzer.TryGetPolyLinear(second.Base, x, out var s2, out var c2)
// Constant multiples of one linear are one linear, not a quadratic.
|| VanishesIdentically((s1 * c2 - s2 * c1).Expand()))
continue;
var quadratic = (first.Base * second.Base).Expand().InnerSimplified;
Entity others = rest;
for (var k = 0; k < factors.Count; k++)
if (k != i && k != j)
others = factors[k].Below ? Over(others, factors[k].Factor) : Times(others, factors[k].Factor);
// Spelled as a quotient where the power is negative, which is how the rules for a
// power of a quadratic read it: as `g Q^(-5/2)` it was as slow as the split form.
var asked = power.IsNegative ? Over(others, MathS.Pow(quadratic, (Number.Rational)(-power))) : Times(others, MathS.Pow(quadratic, power));
if (Integration.ComputeAsAQuestionOfItsOwn(asked, x, integrateByParts) is not { } joined)
return null;
// What is checked is the joined integral against the joined integrand: the constant
// is constant by the argument above, and differentiated it is a sum the sampled
// check does not reduce to zero.
if (!Functions.PartialFractions.DerivativeHoldsAtSampledPoints(joined, asked, x))
return null;
var constant = MathS.Pow(first.Base, power) * MathS.Pow(second.Base, power) / MathS.Pow(quadratic, power);
return constant * joined;
}
return null;
}

/// <summary>
/// A power of a linear that is not whole, times a linear, over a whole power of a quadratic,
/// with the power of the quadratic lowered one at a time:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// over each linear once with the constant that takes in front. First, since it answers
// the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and
// asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere.
// Two linears to one power that is not whole, as one quadratic to that power, before each
// linear is written once: `(2 + 3x)^4/((1 - 2x)^(5/2) (3 + 5x)^(5/2))` took forty-five
// seconds there, and over `(3 - x - 10x^2)^(5/2)` it takes half of one.
if ((answer = IndefiniteIntegralSolver.SolveTwoLinearsToOnePowerAsOneQuadratic(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer;
// A rational function with numbers for coefficients over a denominator past the twelfth
// degree, by Hermite's reduction before anything searches: nothing below answered one.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// 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 to one power that is not whole, integrated as one quadratic to that power with
/// <c>L1^p L2^p/(L1 L2)^p</c> in front, which is constant on each interval it is continuous on.
/// Rubi's 1.1.1.3, which declined the quarter powers and took forty-five seconds over the
/// powers of <c>5/2</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class TwoLinearsToOnePowerIntegralTest
{
private static void DifferentiatesBack(string integrand, Entity[] points, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

var pinned = integral.Substitute("C", 0);
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
pinned = pinned.Substitute(name, value);
original = original.Substitute(name, value);
}
var derivative = pinned.Differentiate("x");
foreach (var at in points)
{
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-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

private static readonly Entity[] TheLine = { "-23/10", "-7/10", "-3/10", "2/5", "9/10", "5/2" };

// Where 1 - 2x and 3 + 5x are positive: outside, the integrand is complex and the answer's
// arcsin is past its real interval, which the evaluator reads as NaN at its default
// precision; the answer agrees with the integrand there to 2000 digits.
private static readonly Entity[] BetweenTheRoots = { "-11/20", "-3/10", "0", "1/5", "9/20" };

[Theory]
[InlineData("(e*x)^(3/2)/((1-x)^(1/4)*(1+x)^(1/4))")]
[InlineData("1/((1-x)^(1/4)*(e*x)^(1/2)*(1+x)^(1/4))")]
public void TwoQuarterPowers(string integrand)
{
DifferentiatesBack(integrand, TheLine, ("e", 1.5));
DifferentiatesBack(integrand, TheLine, ("e", -0.75));
}

[Theory]
[InlineData("(2+3*x)^4/((1-2*x)^(5/2)*(3+5*x)^(5/2))")]
[InlineData("1/((1-2*x)^(5/2)*(2+3*x)^2*(3+5*x)^(5/2))")]
public void TwoPowersOfFiveHalves(string integrand) => DifferentiatesBack(integrand, BetweenTheRoots);
}
}
Loading