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 @@ -175,6 +175,20 @@ the working digits, or `MathS.Settings.PrecisionErrorZeroRange` where a caller h
still that integer, so `e^(-123.456)` is 0 as it was.
[#1338](https://github.com/asc-community/AngouriMath/issues/1338).

### An arctangent over a power of `d (1 + i c x)` is integrated under the tangent

**Answers where there were none.** `(a + b atan(c x))^2/(d + i c d x)^2` ran out of time, and so did seven more of
Rubi's 5.3.4. Under `x = tan(u)/c`, `1 + i tan(u)` is `e^(i u)/cos(u)`, so `x^m (a + b atan(c x))^p/(d (1 ± i c x))^k`
becomes a polynomial in `u` times exponentials and trigonometric functions, and is now integrated so
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a+b*atan(c*x))^2/(d+i*c*d*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 267 characters |
| `"x*(a+b*atan(c*x))^2/(d+i*c*d*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 671 characters |
| `"(a+b*atan(c*x))^2/(1+i*c*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 731 characters |
| `"(a+b*atan(c*x))^3/(d+i*c*d*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 1,202 characters |

### A constant or an exponential evaluated at a higher precision than before has all of its digits

**More digits where there were a hundred.** `pi`, `e` and an exponential, evaluated once at one precision, went on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29569,6 +29569,70 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
return (alpha / (p0 * slope) * product).InnerSimplified;
}

/// <summary>
/// <c>x^m (a + b atan(c x))^p</c> over a power of <c>d (1 +- i c x)</c>, under <c>x = tan(u)/c</c>, where
/// <c>1 + i tan(u)</c> is <c>e^(i u)/cos(u)</c>: the integrand in <c>u</c> is
/// <c>(a + b u)^p (tan(u)/c)^m d^(-k) e^(-+i k u) cos(u)^k/(c cos(u)^2)</c>, a polynomial in <c>u</c> times
/// exponentials and trigonometric functions, and the answer is written back at <c>u = atan(c x)</c>.
/// </summary>
/// <remarks>
/// <c>1 + i tan(u) = (cos(u) + i sin(u))/cos(u)</c> holds for every <c>u</c> where the tangent is defined,
/// and <c>atan(c x)</c> takes each real <c>x</c> into <c>(-pi/2, pi/2)</c>, where it is. Rubi's 5.3.4
/// <c>(a + b atan(c x))^2/(d + i c d x)^2</c> and seven like it ran out of time.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
Arctanf? arctangent = null;
Entity? rate = null;
foreach (var node in numerator.Nodes)
if (node is Arctanf found && found.ContainsNode(x))
{
if (arctangent is not null && arctangent != found)
return null;
if (!TreeAnalyzer.TryGetPolyLinear(found.Argument, x, out var slope, out var offset) || !Functions.PartialFractions.IsZeroAsAValue(offset))
return null;
(arctangent, rate) = (found, slope);
}
if (arctangent is null || rate is null)
return null;
// Below the bar one power of a linear d (1 + s i c x), s = +-1, and nothing else with x.
Entity? linear = null;
var k = 0;
Entity constant = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(denominator))
{
if (!factor.ContainsNode(x)) { constant *= factor; continue; }
var (b, e) = factor is Powf(var pb, Number.Integer { EInteger: var pe }) && pe.Sign > 0 && pe.CanFitInInt32() ? (pb, pe.ToInt32Unchecked()) : (factor, 1);
if (linear is not null)
return null;
(linear, k) = (b, e);
}
if (linear is null || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var lSlope, out var lOffset) || Functions.PartialFractions.IsZeroAsAValue(lOffset))
return null;
// The ratio of slope to constant is +-i c.
var ratio = (lSlope / (lOffset * MathS.i * rate)).InnerSimplified;
int sign;
if (Functions.PartialFractions.IsZeroAsAValue((ratio - 1).InnerSimplified)) sign = 1;
else if (Functions.PartialFractions.IsZeroAsAValue((ratio + 1).InnerSimplified)) sign = -1;
else return null;
var u = Variable.CreateUnique(expr, "u_atan");
// Above the bar: a power of x and the function of the arctangent.
var above = numerator.Substitute(arctangent, u);
if (above.Substitute(x, Number.Integer.Zero).ContainsNode(x))
return null;
var inU = above.Substitute(x, MathS.Tan(u) / rate)
* MathS.Pow(lOffset, Number.Integer.Create(-k)) * MathS.Pow(MathS.e, -sign * k * MathS.i * u) * (k == 2 ? Number.Integer.One : MathS.Pow(MathS.Cos(u), Number.Integer.Create(k - 2)))
/ (constant * rate);
if (Integration.ComputeAsAQuestionOfItsOwn(inU.InnerSimplified, u, integrateByParts) is not { } integrated)
return null;
var answer = integrated.Substitute(u, arctangent).InnerSimplified;
return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer;
}

/// <summary>
/// A power of a constant multiple of a radicand that a logarithm holds under a root,
/// written over that radicand: <c>acosh(c x)</c> is <c>ln(c x + sqrt(c^2 x^2 - 1))</c>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A sum free of the variable that comes to a number multiplied out, as that number,
// before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer;
// An arctangent over a power of d (1 +- i c x), under x = tan(u)/c, where 1 + i tan(u) is
// e^(i u)/cos(u): the rules below ran out of time on `(a + b atan(c x))^2/(d + i c d x)^2`.
if ((answer = IndefiniteIntegralSolver.SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(expr, x, integrateByParts)) is { }) return answer;
// An exponential of i times an inverse trigonometric function written algebraically, at the
// question asked, before the rules that spent the budget: `e^(3 i atan(a + b x))/x^2` ran out of
// time where, rewritten, it is answered in half a second. It is asked again in its place below.
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>
/// <c>x^m (a + b atan(c x))^p</c> over a power of <c>d (1 +- i c x)</c>, under <c>x = tan(u)/c</c>, where
/// <c>1 + i tan(u)</c> is <c>e^(i u)/cos(u)</c>: Rubi's 5.3.4.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnArctangentOverAPowerOfOnePlusICXIntegralTest
{
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}");
}
}

// d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2
// so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too.
[Theory]
[InlineData("(a+b*atan(c*x))^2/(d+i*c*d*x)^2")]
[InlineData("x*(a+b*atan(c*x))^2/(d+i*c*d*x)^3")]
[InlineData("(a+b*atan(c*x))^3/(d+i*c*d*x)^4")]
[InlineData("(a+b*atan(c*x))/(d-i*c*d*x)^3")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25), ("c", 0.4), ("d", 1.5));
DifferentiatesBack(integrand, ("a", -1.5), ("b", -0.25), ("c", -2), ("d", 0.4));
}
}
}
Loading