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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,19 @@ partial fractions divide. It is taken out in front of the integral first
| `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 76,249 characters, the length the content-free one already had |
| `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters |

### An exponential of a symbolic multiple of i arctan is written as powers of the linears

**Answers where there were none.** `(c + a^2 c x^2)^p/e^(2 i p arctan(a x))` was left unevaluated. For a real `L`,
`e^(k i arctan(L))` is `(1 + i L)^(k/2) (1 - i L)^(-k/2)` and `(K (1 + L^2))^q` is `K^q (1 + i L)^q (1 - i L)^q`, for any
`k` and `q`, so the integrand is a product of powers of two linears, and with these exponents a single power
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(c+a^2*c*x^2)^p/e^(2*i*p*atan(a*x))".ToEntity().Integrate("x")` | `integral(...)` | 84 characters |
| `"e^(2*i*p*atan(a*x))*(c+a^2*c*x^2)^p".ToEntity().Integrate("x")` | `integral(...)` | 86 characters |
| `"e^(i*n*atan(a*x))/(c+a^2*c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 123 characters |

### A whole power is taken out of a power of a product, in front of a constant

**Answers where there were none.** `(c sin(a + b x)^3)^(1/3)/x` was declined: nothing read a function under a root of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9678,6 +9678,93 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer &&
return ratio;
}

/// <summary>
/// An exponential of a symbolic multiple of <c>i arctan(L)</c>, <c>L</c> linear, beside
/// powers of multiples of <c>1 + L^2</c>, written as powers of the two linears:
/// <c>e^(k i arctan(L))</c> is <c>(1 + i L)^(k/2) (1 - i L)^(-k/2)</c> and
/// <c>(K (1 + L^2))^q</c> is <c>K^q (1 + i L)^q (1 - i L)^q</c>, each exactly for a real
/// <c>L</c> and any <c>k</c> and <c>q</c>: the two principal arguments are
/// <c>arctan(L)</c> and its negative, and <c>1 + L^2</c> is positive.
/// </summary>
/// <remarks>
/// Rubi's 5.3.6 <c>(c + a^2 c x^2)^p/e^(2 i p arctan(a x))</c> is then
/// <c>c^p (1 - i a x)^(2p)</c>, a power of a linear; it was left unevaluated.
/// <see cref="SolveByWritingAnExponentialOfAnInverseAlgebraically"/> takes a rational
/// multiple, and this the rest.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveAnExponentialOfASymbolicMultipleOfAnArctangentAsPowersOfTheLinears(Entity expr, Entity.Variable x, bool integrateByParts)
{
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) }
};
var factors = Factors(expr, false).ToList();
Entity? argument = null;
Entity? multiple = null;
foreach (var (factor, below) in factors)
{
if (factor is not Powf(var e, var exponent) || e != MathS.e || !exponent.ContainsNode(x))
continue;
if (argument is not null)
return null;
var arctangents = exponent.Nodes.OfType<Arctanf>().Distinct().ToList();
if (arctangents.Count != 1 || !TreeAnalyzer.TryGetPolyLinear(arctangents[0].Argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
return null;
var placeholder = Variable.CreateUnique(exponent, "u_atan");
if (!TreeAnalyzer.TryGetPolyLinear(exponent.Replace(node => node == arctangents[0] ? placeholder : node), placeholder, out var coefficient, out var free)
|| coefficient is null || free is null || coefficient.ContainsNode(x) || !TreeAnalyzer.IsZero(free))
return null;
// Simplified, since `2 i p/i` is not reduced to `2 p` short of it.
var k = (coefficient / MathS.i).Simplify();
// A rational multiple is the other rule's.
if (k.Evaled is Number.Rational)
return null;
argument = arctangents[0].Argument;
multiple = below ? -k : k;
}
if (argument is null || multiple is null)
return null;
var quadratic = 1 + MathS.Sqr(argument);
Entity constant = Number.Integer.One;
Entity plusPower = multiple / 2, minusPower = -multiple / 2;
foreach (var (factor, below) in factors)
{
if (factor is Powf(var e, var exponent) && e == MathS.e && exponent.ContainsNode(x))
continue;
if (!factor.ContainsNode(x))
{
constant = below ? constant / factor : constant * factor;
continue;
}
var (@base, power) = factor is Powf(var b, var q) ? (b, q) : (factor, (Entity)Number.Integer.One);
if (power.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var a2, out var b2, out var c2)
|| !TreeAnalyzer.TryGetPolyQuadratic(quadratic, x, out var a3, out var b3, out var c3)
|| a2 is null || b2 is null || c2 is null || a3 is null || b3 is null || c3 is null || TreeAnalyzer.IsZero(a3))
return null;
var ratio = (a2 / a3).InnerSimplified;
if (!Functions.PartialFractions.IsZeroAsAValue((b2 - ratio * b3).InnerSimplified)
|| !Functions.PartialFractions.IsZeroAsAValue((c2 - ratio * c3).InnerSimplified))
return null;
var signed = below ? -power : power;
constant *= MathS.Pow(ratio, signed);
plusPower += signed;
minusPower += signed;
}
// Simplified, x-free and small: `-p + p` is a zero power only so.
plusPower = plusPower.Simplify();
minusPower = minusPower.Simplify();
Entity written = constant;
if (!TreeAnalyzer.IsZero(plusPower))
written *= MathS.Pow(1 + MathS.i * argument, plusPower);
if (!TreeAnalyzer.IsZero(minusPower))
written *= MathS.Pow(1 - MathS.i * argument, minusPower);
return Integration.ComputeAsAQuestionOfItsOwn(written, x, integrateByParts);
}

/// <summary>
/// An exponential of <c>i</c> times an inverse trigonometric function, written
/// algebraically: <c>e^(i arctan(L))</c> is <c>(1 + i L)/sqrt(1 + L^2)</c>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// 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.
if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveByWritingAnExponentialOfAnInverseAlgebraically(expr, x, integrateByParts)) is { }) return answer;
// The same for a symbolic multiple of the arctangent, beside powers of its quadratic.
if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveAnExponentialOfASymbolicMultipleOfAnArctangentAsPowersOfTheLinears(expr, x, integrateByParts)) is { }) return answer;
// A polynomial over a symbolic quartic that splits into two quadratics in a hidden variable,
// palindromic or even after a shift: the rules below spent seconds declining both.
if ((answer = IndefiniteIntegralSolver.SolveAPolynomialOverASymbolicQuarticThatSplitsIntoTwoQuadratics(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// 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>
/// An exponential of a symbolic multiple of <c>i arctan(a x)</c> beside a power of its quadratic,
/// written as powers of the two linears: Rubi's 5.3.6
/// <c>(c + a^2 c x^2)^p/e^(2 i p arctan(a x))</c>, which was left unevaluated.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnExponentialOfASymbolicMultipleOfAnArctangentIntegralTest
{
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}");
}
}

[Theory]
[InlineData("(c+a^2*c*x^2)^p/e^(2*i*p*atan(a*x))")]
[InlineData("e^(2*i*p*atan(a*x))*(c+a^2*c*x^2)^p")]
[InlineData("e^(i*n*atan(a*x))/(c+a^2*c*x^2)")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 1.3), ("c", 0.7), ("p", 0.4), ("n", 0.6));
DifferentiatesBack(integrand, ("a", -0.5), ("c", -1.5), ("p", 1.3), ("n", -2.2));
}
}
}
Loading