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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,20 @@ power up each step, and what reaches the first power is integrated once over the
| `"1/(a + b*x^2 + c*x^4)^3".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"x/(a + b*x^3 + c*x^6)^2".ToEntity().Integrate("x")` | `integral(...)` | a polynomial over `a + b x^3 + c x^6`, and the integral of a polynomial over it |

### A polynomial over a power of one linear with a symbol in it is written in powers of the linear

**Answers where there were none.** `t^9/(a + b t)^8` is a polynomial and eight powers of `1/(a + b t)`,
and was declined by 2.5.0. On the unreleased master it was answered after forty-six seconds: divided
out and decomposed with the symbols in it, it went round the substitution search a dozen levels deep.
With nothing else below the bar and a symbol in the linear, the polynomial is written in powers of
the linear at its root now, and each piece is the table's
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"t^9/(a + b*t)^8".ToEntity().Integrate("t")` | `integral(...)` | powers of `a/b + t` and a logarithm, in 0.03 s |
| `"x^4/(a + b*sqrt(x))^8".ToEntity().Integrate("x")` | `integral(...)` | the same in `sqrt(x)`, in 0.5 s; a minute on the unreleased master |

### A polynomial over a power of a quadratic with a sum of symbols in it is reduced

**Answers where there were none.** The reduction of `N(x)/Q(x)^n` divides `N` by the quadratic a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,11 @@ Entity Back(Entity mapped)
return SolveByPartialFractions(reducedTop / reducedBottom, x, integrateByParts)
?? Integration.ComputeIndefiniteIntegral(reducedTop / reducedBottom, x, integrateByParts);

// A polynomial over a power of one linear with a symbol in it, in powers of the linear at
// its root, before the division: see SolveByReducingThePolynomialOverALinearFactor.
if (SolveByReducingThePolynomialOverALinearFactor(expr, x, integrateByParts, alone: true) is { } overThePowersOfTheLinear)
return overThePowersOfTheLinear;

// The helper answers null for a fraction that is already proper, so this cannot
// fire on one and recurse into the problem it started from. The check on the
// quotient is the second half of that guarantee: a division that came back with
Expand Down Expand Up @@ -2207,6 +2212,18 @@ over is Entity.Powf(var @base, var power) ?
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByReducingThePolynomialOverALinearFactor(Entity expr, Entity.Variable x, bool integrateByParts)
=> SolveByReducingThePolynomialOverALinearFactor(expr, x, integrateByParts, alone: false);

/// <summary>
/// <see cref="SolveByReducingThePolynomialOverALinearFactor(Entity, Entity.Variable, bool)"/>,
/// or with <paramref name="alone"/> a polynomial over a power of one linear and nothing else,
/// that linear with a symbol in it: <c>t^9/(a + b t)^8</c> is a polynomial and eight powers of
/// <c>1/(a + b t)</c>, each the table's. <see cref="SolveByPartialFractions"/> asks it so before
/// it divides: divided out and decomposed with the symbols in it, that one went round the
/// substitution search a dozen levels deep and took forty-six seconds, and with numbers in
/// place of <c>a</c> and <c>b</c> it took sixty milliseconds.
/// </summary>
private static Entity? SolveByReducingThePolynomialOverALinearFactor(Entity expr, Entity.Variable x, bool integrateByParts, bool alone)
{
if (!TryReadAsQuotient(expr, out var numerator, out var denominator))
return null;
Expand Down Expand Up @@ -2241,11 +2258,13 @@ over is Entity.Powf(var @base, var power) ?
somethingElse = true;
rest *= factor;
}
if (linear is null || !somethingElse)
if (linear is null || (alone ? somethingElse || rest.ContainsNode(x) || multiplicity < 2 : !somethingElse))
return null;

if (!TreeAnalyzer.TryGetPolynomial(linear, x, out var line))
return null;
if (alone && !line.Values.Any(coefficient => coefficient.Vars.Any()))
return null;
var beta = line[EInteger.One];
var alpha = line.TryGetValue(EInteger.Zero, out var constantTerm) ? constantTerm : Number.Integer.Zero;
var root = Functions.PartialFractions.Bare((-alpha / beta).InnerSimplified);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// 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>
/// A polynomial over a power of one linear with a symbol in it, written in powers of the
/// linear at its root: <c>t^9/(a + b t)^8</c> is a polynomial and eight powers of
/// <c>1/(a + b t)</c>. Divided out and decomposed with the symbols in it, that one took
/// forty-six seconds, and <c>x^4/(a + b sqrt(x))^8</c>, which is it under <c>x = t^2</c>,
/// a minute; the sizes here are the ones that were slow, so that a regression shows as a
/// slow suite rather than as a clock in a test.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class PolynomialOverAPowerOfASymbolicLinearTest
{
[Theory]
[InlineData("x^9/(a + b*x)^8")]
[InlineData("x^6/(a + b*x)^5")]
[InlineData("x^9/(a + x)^8")]
[InlineData("(c + d*x)^3/(a + b*x)^5")]
[InlineData("x^4/(a + b*sqrt(x))^8")]
public void InPowersOfTheLinear(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 2.3).Substitute("b", 0.7).Substitute("c", 1.3).Substitute("d", 1.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.7, -0.9, 0.3, 0.8, 1.6, 2.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12)
continue;
compared++;
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.True(compared >= 3, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading