Skip to content
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,22 @@ digits, and `e^(1/3)` the same. Each is now worked out in the precision asked fo
| `MathS.pi.Evaled` under 300 digits, after a read under 100 | 100 digits | 300 digits |
| `"e^(1/3)".Evaled` under 300 digits, after a read under 100 | 100 digits | 300 digits |

### An exponential of i times an arctangent of a linear is written algebraically before the other rules

**Answers where there were none.** `e^(3 i atan(a + b x))/x^2` ran out of time, and so did fifteen more of Rubi's
5.3.6, all `e^(n i atan(a + b x))` over a power of x or `e^(n i atan(a x))` beside a high power of `c + a^2 c x^2`.
The exponential is now written as powers of linears, `(1 + i L)^(n/2) (1 - i L)^(-n/2)`, at the question asked, ahead
of the rules that spent the budget, and for every `n` beside a pole at 0 where the argument has a constant term. A
piecewise answer is checked for `NaN` without `Simplify`, whose search on one of them ran past a minute
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"e^(3*i*atan(a+b*x))/x^2".ToEntity().Integrate("x")` | `integral(...)` | 1,945 characters |
| `"1/(e^(3*i*atan(a+b*x))*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 8,542 characters |
| `"e^(3*i*atan(a+b*x))/x^4".ToEntity().Integrate("x")` | `integral(...)` | 35,041 characters |
| `"e^(6*i*atan(a*x))*x^2/(c+a^2*c*x^2)^19".ToEntity().Integrate("x")` | `integral(...)` | 233 characters |

### A string parsed once is parsed again under the other downcasting setting

**A different number.** `MathS.FromString` remembered what it had parsed, keyed on the parsing setting alone. A string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9696,7 +9696,11 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer &&
var quadraticElsewhere = expr.Nodes.Any(node => node is Powf(var q, _) && q.ContainsNode(x) && !q.Nodes.Any(inner => inner is Arctanf or Arcsinf or Arccosf)
&& TreeAnalyzer.TryGetPolyQuadratic(q, x, out var leading, out _, out _) && !TreeAnalyzer.IsZero(leading));
// The variable outside the exponentials.
var xElsewhere = expr.Replace(node => node is Powf(var e, var power) && e == MathS.e && power.ContainsNode(x) ? Number.Integer.One : node).ContainsNode(x);
var withoutTheExponentials = expr.Replace(node => node is Powf(var e, var power) && e == MathS.e && power.ContainsNode(x) ? Number.Integer.One : node);
var xElsewhere = withoutTheExponentials.ContainsNode(x);
// The variable in a denominator, a pole beside the exponentials: not `x^2/e^(i arctan(a + b x))`.
var xInADenominator = withoutTheExponentials.Nodes.Any(node => node is Divf(_, var denominator) && denominator.ContainsNode(x)
|| node is Powf(var @base, Number.Real { IsNegative: true }) && @base.ContainsNode(x));
var rewritten = expr.Replace(node =>
{
if (node is not Powf(var @base, var exponent) || @base != MathS.e || !exponent.ContainsNode(x))
Expand Down Expand Up @@ -9740,7 +9744,14 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer &&
// ran past thirty seconds, and `x^3 e^(-i arctan(a x))` came out in 26 thousand
// characters where so it is 218. Alone the exponential is answered shorter as the
// root, and for a power not whole the quotient's power is.
if ((n == Number.Integer.One || n == Number.Integer.MinusOne) && !quadraticElsewhere && xElsewhere)
// So for every n beside a pole at 0 and an argument with a constant term:
// `e^(3 i arctan(a + b x))/x^2` as `(1 + i L)^3 (1 + L^2)^(-3/2)` ran out of time, and
// as the two powers it is answered in half a second. Not for `a x` or a polynomial
// beside it, which the root answers: `x^3 e^(-(3/2) i arctan(a x))` and
// `e^(3 i arctan(a x))/x` were answered and as the two powers ran out of time.
if (!quadraticElsewhere && xElsewhere && (n == Number.Integer.One || n == Number.Integer.MinusOne
|| xInADenominator && TreeAnalyzer.TryGetPolyLinear(argument, x, out _, out var argumentConstant)
&& argumentConstant is { } && !TreeAnalyzer.IsZero(argumentConstant)))
{
var half = Number.Rational.Create(n.ERational.Divide(2));
return MathS.Pow(1 + MathS.i * argument, half) * MathS.Pow(1 - MathS.i * argument, Number.Rational.Create(half.ERational.Negate()));
Expand Down Expand Up @@ -9787,7 +9798,9 @@ node is Powf(var q, Number.Rational r) && r is not Number.Integer && q != first
// non-existence is not given.
if (expr.Vars.Any(symbol => symbol != x) && !Functions.PartialFractions.HoldsAtSampledPoints(answer.Differentiate(x), expr, x))
return null;
if (answer.Nodes.Any(node => node is Piecewise) && answer.Simplify().Nodes.Any(node => node == MathS.NaN))
// InnerSimplified rather than Simplify: the search ran past a minute on the piecewise answer to
// `e^(3 i atan(a + b x))/x^2`, ready and checked in a quarter of a second.
if (answer.Nodes.Any(node => node is Piecewise) && answer.InnerSimplified.Nodes.Any(node => node == MathS.NaN))
return null;
return answer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,10 @@ 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 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.
if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveByWritingAnExponentialOfAnInverseAlgebraically(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,63 @@
//
// 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 i times an arctangent of a linear beside a power of x, written as two powers of
/// linears, `(1 + i L)^(n/2) (1 - i L)^(-n/2)`, at the question asked: Rubi's 5.3.6
/// <c>e^(2 i atan(a + b x))/x^2</c> and the like ran out of time before the rule was reached.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnExponentialOfAnArctangentBesideAPowerOfXIntegralTest
{
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("e^(2*i*atan(a+b*x))/x^2")]
[InlineData("e^(3/2*i*atan(a+b*x))/x^2")]
[InlineData("e^(3*i*atan(a+b*x))/x")]
// Answered as a piecewise, which the check for a NaN simplified for over a minute.
[InlineData("e^(3*i*atan(a+b*x))/x^2")]
[InlineData("1/(e^(3*i*atan(a+b*x))*x^3)")]
[InlineData("x*e^(-2*i*atan(a+b*x))")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25));
DifferentiatesBack(integrand, ("a", -0.75), ("b", 0.4));
}
}
}
Loading