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 @@ -310,6 +310,20 @@ carried the discriminant's being real in its condition, and held nowhere when it
| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().DomainCondition` | `false` | `not x = 0` |
| `"1/(sqrt(a + i*a*x)*(c + d*x)^(3/2)*(a - i*a*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer with no value at any point | 3,267 characters, defined |

### A rational function over the quadratic of an inverse tangent beside it is split

**Answers where there were none.** `x^4 atan(a x)/(c + a^2 c x^2)` ran out of time, and so did three more of Rubi's 5.3.4.
The rational factor is now divided down and split over `c + a^2 c x^2` and the powers of x, and each part beside the
inverse tangent is integrated on its own
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^4*atan(a*x)/(c+a^2*c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 233 characters |
| `"x^2*atan(a*x)/(c+a^2*c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 126 characters |
| `"atan(a*x)/(x^2*(c+a^2*c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 612 characters |
| `"atan(a*x)/(x^4*(c+a^2*c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 894 characters |

### A piecewise case whose condition is undefined is passed over

**A different answer.** A case whose predicate evaluates to NaN -- an order comparison of a number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26445,6 +26445,101 @@ static void Add(Dictionary<int, ERational> into, int k, ERational value)
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A rational function over the quadratic an inverse tangent's derivative is written over,
/// times a function of that inverse tangent, <c>x^4 atan(a x)/(c + a^2 c x^2)</c>, with the
/// rational function split into partial fractions over its written factors and each piece
/// beside the function its own question: <c>atan(a x)/(1 + a^2 x^2)</c> is
/// <c>atan(a x)^2/(2a)</c>, and the rest is a power of x beside the inverse tangent.
/// </summary>
/// <remarks>
/// The rules before it timed out on Rubi's 5.3.4 <c>x^4 atan(a x)/(c + a^2 c x^2)</c> and
/// <c>atan(a x)/(x^4 (c + a^2 c x^2))</c>, and declined <c>x^2 atan(a x)/(c + a^2 c x^2)</c>.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveARationalFunctionOverTheQuadraticOfAnArctangentBesideIt(Entity expr, Entity.Variable x)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch
{
Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)),
Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)),
_ => new[] { (e, below) }
};
Entity? function = null;
Entity above = Number.Integer.One, below = Number.Integer.One;
foreach (var (factor, isBelow) in Factors(expr, false))
{
if (factor.Nodes.Any(node => node is Arctanf && node.ContainsNode(x)))
{
if (function is not null || isBelow)
return null;
function = factor;
}
else if (isBelow)
below *= factor;
else
above *= factor;
}
if (function is null || !below.ContainsNode(x))
return null;
var arctangents = function.Nodes.Where(node => node is Arctanf && node.ContainsNode(x)).Distinct().ToList();
if (arctangents.Count != 1 || arctangents[0] is not Arctanf(var argument)
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var intercept)
|| !Functions.PartialFractions.IsZeroAsAValue(intercept))
return null;
// The rest is a rational function in x, and one written factor below the bar is a
// multiple of 1 + (a x)^2.
var quadratic = 1 + MathS.Sqr(slope) * MathS.Sqr(x);
var (numerator, denominator) = Functions.SingleQuotient.Of(above / below);
if (!IsARationalFunction(numerator / denominator, x)
|| !Mulf.LinearChildren(denominator).Any(part => part.ContainsNode(x)
&& part.Substitute(x, Number.Integer.Zero).InnerSimplified is var atZero
&& !Functions.PartialFractions.IsZeroAsAValue(atZero)
&& Functions.PartialFractions.IsZeroAsAValue((part - atZero * quadratic).InnerSimplified)))
return null;
// An improper one divided first: `x^2/(c + a^2 c x^2)` is `1/(a^2 c) - 1/(a^2 (c + a^2 c x^2))`.
var pieces = new List<Entity>();
if (!IsProperByDegree(numerator, denominator, x))
{
if (TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is not var (quotient, properPart))
return null;
pieces.Add(quotient);
// The generic case, as the integrator answers it: the remainder carries the
// condition that the leading coefficient is not zero.
properPart = properPart.Replace(node => node is Providedf(var expression, _) ? expression : node).InnerSimplified;
(numerator, denominator) = Functions.SingleQuotient.Of(properPart);
if (Functions.PartialFractions.IsZeroAsAValue(numerator))
numerator = Number.Integer.Zero;
}
Entity decomposition;
if (numerator == Number.Integer.Zero)
decomposition = Number.Integer.Zero;
else if (!Functions.PartialFractions.TrySplitOverWrittenFactors(numerator, denominator, x, out var split))
{
if (pieces.Count == 0)
return null;
decomposition = numerator / denominator;
}
else
decomposition = split;
// The split comes back as one sum over a factor free of x, `(0 + 1/(c x^2) + ...)/1`.
var (terms, divisor) = decomposition is Divf(var sum, var free) && !free.ContainsNode(x) ? (sum, free) : (decomposition, (Entity)Number.Integer.One);
pieces.AddRange(Sumf.LinearChildren(terms).Where(term => !Functions.PartialFractions.IsZeroAsAValue(term)).Select(term => term / divisor));
if (pieces.Count < 2)
return null;
Entity answer = Number.Integer.Zero;
foreach (var piece in pieces)
{
if (Integration.ComputeAsAQuestionOfItsOwn((piece * function).InnerSimplified, x, integrateByParts: true) is not { } integrated)
return null;
answer += integrated;
}
answer = answer.InnerSimplified;
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol
/// among the coefficients: <c>sin(x)/(a + b sin(x) + c sin(x)^2)</c>, and the same with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the
// binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer;
// A rational function over the quadratic of an inverse tangent beside it, split, before the
// rules that timed out on `x^4 atan(a x)/(c + a^2 c x^2)`.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOverTheQuadraticOfAnArctangentBesideIt(expr, x)) is { }) return answer;
// A power of x times a power of a sum holding an inverse function of x^n, under t = x^n,
// before the rules that timed out on `x^3 (a + b atanh(c x^2))^2`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(expr, x)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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 rational function over the quadratic an inverse tangent's derivative is written over, beside
/// that inverse tangent, split into partial fractions: Rubi's 5.3.4 <c>x^4 atan(a x)/(c + a^2 c x^2)</c>
/// and <c>atan(a x)/(x^4 (c + a^2 c x^2))</c>, which timed out.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ARationalFunctionOverTheQuadraticOfAnArctangentIntegralTest
{
private static void DifferentiatesBack(string integrand, 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 new[] { -2.3, -0.7, 0.4, 1.9 })
{
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}");
}
}

// The quadratic's factor is q, not Rubi's c: the helper takes C, and c is too close.
[Theory]
[InlineData("x^4*atan(a*x)/(q+a^2*q*x^2)")]
[InlineData("x^2*atan(a*x)/(q+a^2*q*x^2)")]
[InlineData("atan(a*x)/(x^2*(q+a^2*q*x^2))")]
[InlineData("atan(a*x)/(x^4*(q+a^2*q*x^2))")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 1.3), ("q", 0.7));
DifferentiatesBack(integrand, ("a", -0.4), ("q", -2));
}
}
}
Loading