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
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,25 @@ after it
|---|---|---|
| `"(a*c + b*c*x)^(-3-2*p)*(f + g*x)*(a^2 + 2*a*b*x + b^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)`; an answer with no value on the unreleased master | the antiderivative |

### A symbolic cubic's linear factors are found before the partial fractions

**Answers where there were none.** `1/(a c h + (b c h + a d h + a c f) x + (b d h + b c f + a d f) x^2 + b d f x^3)`,
`(a + b x)(c + d x)(h + f x)` multiplied out, was declined, with its square and cube, Rubi's 1.3.1. A polynomial of
the third degree or more with symbols in it, whose leading and constant coefficients are single terms, is tried
for linear factors `u ± v x`, with `u` a divisor of the constant term and `v` one of the leading coefficient, as for
the rational roots of a polynomial over the integers. The factors that divide it exactly are written apart, and
those the numerator also holds are cancelled first. The same split answers three rows of Rubi's 4.7.2 and 4.7.7,
whose denominator in the tangent is a product of linears
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(a*c*h + (b*c*h + a*d*h + a*c*f)*x + (b*d*h + b*c*f + a*d*f)*x^2 + b*d*f*x^3)".ToEntity().Integrate("x")` | `integral(...)` | the antiderivative, in logarithms of the three linears, 288 characters |
| the same cubic squared, and cubed | `integral(...)` | answered, in 3,047 and 10,662 characters |
| `"sec(c + d*x)^6/(a*cos(c + d*x) + b*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | answered, in 5,573 characters |
| `"sec(c + d*x)^3/(a*cos(c + d*x) + b*sin(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | answered, in 9,322 characters |
| `"1/(a*sec(x) + b*tan(x))^4".ToEntity().Integrate("x")` | `integral(...)` | answered, in 6,548 characters |

### An odd power of a quadratic's derivative over a power of the quadratic is integrated in the quadratic

**Shorter answers, and answers where there were none.** `(b + 2c x)^9/(a + b x + c x^2)^3` was answered through the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,18 @@ Entity Back(Entity mapped)
if (SolveByReducingThePolynomialOverALinearFactor(expr, x, integrateByParts, alone: true) is { } overThePowersOfTheLinear)
return overThePowersOfTheLinear;

// A symbolic polynomial of the third degree or more whose leading and constant coefficients
// are monomials, with the linear factors that divide it written apart: Rubi's 1.3.1 has
// `(a + b x)(c + d x)(e + f x)` multiplied out below the bar, which nothing read.
// The linears the numerator holds as well are divided out of both: the half-angle form of
// 4.7.7's `1/(a sec(x) + b tan(x))^4` has `1 - t` and `1 + t` four times each below the bar
// and `2 (1 - t^2)^8` above it, and uncancelled the partial fractions ran past two minutes
// where it was declined in three seconds. Through the partial fractions only: handed to the
// whole integrator where they decline, it went to the substitution search.
if (WithSymbolicLinearFactorsFound(numerator, denominator, x) is var (aboveTheFound, belowTheFound)
&& SolveByPartialFractions(aboveTheFound / belowTheFound, x, integrateByParts) is { } overTheFoundLinears)
return overTheFoundLinears;

// A symbolic quadratic whose discriminant is a square in the symbols, written as its two
// linears before the division: the numerator may share one of them, and over the
// quadratic as written the division of `(d + e x)^8` ran past a minute.
Expand Down Expand Up @@ -15177,6 +15189,115 @@ private static bool SharesAFactor(Entity above, Entity below, Entity.Variable x)
return false;
}

/// <summary>
/// <paramref name="denominator"/> with each written factor of the third degree or more in
/// <paramref name="x"/>, with symbols in it, whose leading and constant coefficients are
/// single terms, written as the linear factors <c>u ± v x</c> that divide it exactly and what
/// is left: <c>u</c> a divisor of the constant term and <c>v</c> one of the leading
/// coefficient, as for the rational roots of a polynomial over the integers.
/// <see langword="null"/> where no factor has one.
/// </summary>
private static (Entity Numerator, Entity Denominator)? WithSymbolicLinearFactorsFound(Entity numerator, Entity denominator, Entity.Variable x)
{
var changed = false;
var cancelled = false;
Entity product = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(denominator))
{
var (@base, power) = factor is Powf(var b, Number.Integer { EInteger.Sign: > 0 } p) ? (b, (Entity)p) : (factor, (Entity)Number.Integer.One);
if (@base is not (Sumf or Minusf) || !@base.ContainsNode(x) || !@base.Vars.Any(v => v != x))
{
product *= factor;
continue;
}
var variables = @base.Vars.Concat(numerator.Vars).Distinct().OrderBy(v => v.Name, System.StringComparer.Ordinal).ToList();
if (variables.Count > MultivariatePolynomial.MaxVariables)
{
product *= factor;
continue;
}
var indices = new Dictionary<Variable, int>();
for (var i = 0; i < variables.Count; i++)
indices[variables[i]] = i;
var at = indices[x];
if (MultivariatePolynomial.TryParse(@base, indices) is not { } polynomial || polynomial.DegreeIn(at) < 3
|| !polynomial.CoefficientsIn(at).TryGetValue(polynomial.DegreeIn(at), out var leading) || leading.TermCount != 1
|| !polynomial.CoefficientsIn(at).TryGetValue(0, out var constant) || constant.TermCount != 1)
{
product *= factor;
continue;
}
var remaining = polynomial;
var found = new List<MultivariatePolynomial>();
var xMonomial = MultivariatePolynomial.PackMonomial(at, 1);
foreach (var u in MonomialDivisors(constant, variables.Count))
foreach (var v in MonomialDivisors(leading, variables.Count))
foreach (var sign in new[] { ERational.One, ERational.One.Negate() })
{
if (!v.Monomials.Any())
continue;
var linear = u.Add(MultivariatePolynomial.Term(variables.Count, v.Monomials.First() | xMonomial, v.CoefficientOf(v.Monomials.First()).Multiply(sign)));
while (remaining.DegreeIn(at) >= 2 && remaining.DivideExact(linear) is { } quotient)
{
found.Add(linear);
remaining = quotient;
}
}
if (found.Count == 0)
{
product *= factor;
continue;
}
// Each found linear the numerator holds is divided out of it, once per power it stands to.
var multiplicity = ((Number.Integer)power).EInteger.ToInt32Unchecked();
var above = MultivariatePolynomial.TryParse(numerator, indices);
foreach (var linear in found)
{
var left = multiplicity;
while (left > 0 && above?.DivideExact(linear) is { } fewer)
{
above = fewer;
left--;
cancelled = true;
}
if (left > 0)
product *= left == 1 ? linear.ToEntity(variables) : MathS.Pow(linear.ToEntity(variables), left);
}
if (cancelled && above is { })
numerator = above.ToEntity(variables);
product *= power == Number.Integer.One ? remaining.ToEntity(variables) : MathS.Pow(remaining.ToEntity(variables), power);
changed = true;
}
return changed ? (numerator, product) : null;

// The divisors of a single term: every product of powers of its variables up to theirs,
// with the coefficient one or the term's own.
static IEnumerable<MultivariatePolynomial> MonomialDivisors(MultivariatePolynomial term, int count)
{
var monomial = term.Monomials.First();
var coefficient = term.CoefficientOf(monomial);
var powers = Enumerable.Range(0, count).Select(v => MultivariatePolynomial.PowerOfMonomial(monomial, v)).ToArray();
var divisors = new List<ulong> { 0 };
for (var v = 0; v < count; v++)
{
var next = new List<ulong>();
foreach (var partial in divisors)
for (var k = 0; k <= powers[v]; k++)
next.Add(partial | (k == 0 ? 0 : MultivariatePolynomial.PackMonomial(v, k)));
divisors = next;
if (divisors.Count > 256)
yield break;
}
var magnitude = coefficient.Sign < 0 ? coefficient.Negate() : coefficient;
foreach (var divisor in divisors)
{
yield return MultivariatePolynomial.Term(count, divisor, ERational.One);
if (!magnitude.Equals(ERational.One))
yield return MultivariatePolynomial.Term(count, divisor, magnitude);
}
}
}

/// <summary>
/// <paramref name="expr"/> with each sum among its factors, or the base of a power of one,
/// written as <see cref="MultivariatePolynomial.ToEntity"/> writes it: the spelling
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// 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 symbolic cubic whose leading and constant coefficients are single terms, written as the
/// linear factors that divide it before the partial fractions. Rubi's 1.3.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class SymbolicLinearFactorsOfACubicIntegralTest
{
[Theory]
[InlineData("1/(a*c*h + (b*c*h + a*d*h + a*c*f)*x + (b*d*h + b*c*f + a*d*f)*x^2 + b*d*f*x^3)")]
[InlineData("1/(a*c*h + (b*c*h + a*d*h + a*c*f)*x + (b*d*h + b*c*f + a*d*f)*x^2 + b*d*f*x^3)^2")]
public void OverTheFoundLinears(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 40000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.4).Substitute("c", 1.9).Substitute("d", 1.6).Substitute("f", 0.7).Substitute("h", 0.45);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -2.1, -0.4, 0.3, 0.6, 1.6, 2.5 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.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}");
}
}
}
Loading