From 5757714d61fd1dd10153ec967a8c796485044004 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 31 Oct 2026 19:48:19 +0000 Subject: [PATCH] arctan and arccot are undefined at their poles arctan(z) is (i/2)(ln(1 - iz) - ln(1 + iz)), with poles where 1 + z^2 = 0, at i and -i, where it evaluates to an infinity as ln(0) does. arccot(z) is arctan(1/z) and has the same two. Both conditions said True, so arctan(x) - arctan(x) simplified to 0, which has a value at i. They are now not 1 + x^2 = 0, as the tangent's excludes its own poles. The linear-over-quadratic integral wrote its arctangent part even when the numerator was a multiple of the denominator's derivative and left no constant for it, 0 * arctan(2x/2). With the poles in arctan's condition that zero kept them, and x/(x^2 + 1) was answered with provided not 1 + (2x/2)^2 = 0 beside a logarithm that has no value there either: ten tests, through the substitution u = tan(c + dx) as well. It now leaves the part out, as the higher powers of the quadratic already did. An antiderivative that keeps an arctangent can list its poles among the points it has no value at. They say nothing new, and the redundant-condition step cannot see that, since Simplify spells the condition and the arctangent's argument differently. x^2 coth(a + 2 ln(x))^2 is the one test that notices: 1,128 characters of raw answer went to 1,382, against a bound of 1,300 meant to catch the 3,577 of a split on a symbol's sign, so the bound is 1,400. ArctangentPolesTest checks the condition at both poles and off them, and that the two differences have no value at i. The suite, the trio, F#, AOT and the eight harnesses pass. Fixes #2256. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 16 ++++++ Sources/.editorconfig | 3 ++ .../Integration/IntegralPatterns.cs | 12 ++++- ...luation.Continuous.Trigonometry.Classes.cs | 10 +++- ...ntialBesideAFoldedLogarithmIntegralTest.cs | 7 ++- .../Convenience/ArctangentPolesTest.cs | 51 +++++++++++++++++++ 6 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 Sources/Tests/UnitTests/Convenience/ArctangentPolesTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 7a602ae02..5ea70d34e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -921,6 +921,22 @@ conjugate as `0 - 1i`, in decimals, on the one instance that every `i` is, in ev |---|---|---| | `Complex.ImaginaryOne.Conjugate`, after `(1 / Complex.ImaginaryOne).Evaled` with `DowncastingEnabled` off | `0 - 1i` | `-i` | +### `arctan` and `arccot` are undefined at their poles + +**Silent: a simplification that cancels an `arctan` or an `arccot` carries `not 1 + x ^ 2 = 0`, where it carried +nothing.** Both have poles at `i` and `-i`, where they evaluate to an infinity, as `ln(0)` does, and the tangent's +condition excludes its own poles the same way. Their condition said they were defined everywhere, so +`arctan(x) - arctan(x)` simplified to `0`, which has a value at `i`. An antiderivative that keeps an `arctan` can now +list its poles beside the other points it has no value at, which says nothing new +([#2256](https://github.com/asc-community/AngouriMath/issues/2256)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"arctan(x) - arctan(x)".ToEntity().Simplify()` | `0` | `0 provided not 1 + x ^ 2 = 0` | +| `"arccot(x) - arccot(x)".ToEntity().Simplify()` | `0` | `0 provided not 1 + x ^ 2 = 0` | +| `"arctan(x)".ToEntity().DomainCondition` | `True` | `not 1 + x ^ 2 = 0` | +| `"x^2*coth(a + 2*ln(x))^2".ToEntity().Integrate("x").Simplify()` | `… + C + x ^ 3 / 3` | `… + C + x ^ 3 / 3 provided not x ^ 2 / e ^ (-a) + 1 = 0 and not x ^ 2 / (-e ^ (-a)) + 1 = 0` | + ### `max` and `min` are defined where their arguments are real **Silent: `max(x, x)` and `min(x, x)` simplify to `x provided x in RR`, where they were `x`.** The complex numbers are diff --git a/Sources/.editorconfig b/Sources/.editorconfig index d3925edc9..ac7671178 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -235,6 +235,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [AngouriMath/Core/Budgets/*.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Convenience/ArctangentPolesTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Algebra/SolveTest/ModulusEquationTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs index d9b98842a..3fb18a0b9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs @@ -1479,16 +1479,26 @@ private static bool IsAnEvenRootOfASymbol(Entity expr) /// and the second is the constant-numerator case above. /// /// + /// /// With a a symbol the constant-numerator case is a piecewise with an arm for /// a = 0, and on that arm the multiple of the derivative, which divided by /// a, is not the integrand's: there it is (px + q)/(bx + c), which is /// px/b + (q - pc/b) ln(bx + c)/b. Timofeev's (b1 + c1 x)/(a + 2b x + c x^2) /// had no antiderivative, with the arm declined for the symbol in front. + /// + /// + /// A numerator that is a multiple of the derivative leaves no constant part, and the + /// arctangent it would have is left out rather than multiplied by zero, as the higher + /// powers leave it out: 0 * arctan(2x/2) is 0 only where the arctangent has + /// a value, so x/(x^2 + 1) was answered with provided not 1 + (2x/2)^2 = 0 + /// once the arctangent's poles were in its condition, beside a logarithm that has none + /// there either. https://github.com/asc-community/AngouriMath/issues/2256 + /// /// private static Entity IntegrateLinearOverQuadratic( Entity p, Entity q, Entity a, Entity b, Entity c, Entity denominator, Entity.Variable x) => WithTheArmForAZeroLeadingCoefficient( - IntegrateRationalQuadratic(q - p * b / (2 * a), a, b, c, x), + TreeAnalyzer.IsZero(q - p * b / (2 * a)) ? Entity.Number.Integer.Zero : IntegrateRationalQuadratic(q - p * b / (2 * a), a, b, c, x), a, p / (2 * a) * AntiderivativeLog(denominator), LinearOverPowerOfLinear(p, q, b, c, 1, x)); diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs index 5f8d48127..6395a3129 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs @@ -228,7 +228,11 @@ Set.Interval interval when IntervalArithmetic.MonotoneOn(interval, -1, 1, increa public partial record Arctanf { - private protected override Entity IntrinsicCondition => Boolean.True; + // arctan(z) = (i/2)(ln(1 - iz) - ln(1 + iz)) has poles where 1 + z^2 = 0, at z = i and z = -i, where it + // evaluates to an infinity, as ln(0) does: undefined there, as the tangent is where its cosine is 0. + // The condition said True, and arctan(x) - arctan(x) simplified to 0, which has a value at i. + // https://github.com/asc-community/AngouriMath/issues/2256 + private protected override Entity IntrinsicCondition => !(1 + Argument.Pow(2)).EqualTo(0); /// protected override Entity InnerSimplify(bool isExact) => @@ -254,7 +258,9 @@ Set.Interval interval when IntervalArithmetic.Monotone(interval, increasing: tru public partial record Arccotanf { - private protected override Entity IntrinsicCondition => Boolean.True; + // arccot(z) = arctan(1/z), so its poles are arctan's, z = i and z = -i. At 0 it is pi/2. + // https://github.com/asc-community/AngouriMath/issues/2256 + private protected override Entity IntrinsicCondition => !(1 + Argument.Pow(2)).EqualTo(0); /// protected override Entity InnerSimplify(bool isExact) => diff --git a/Sources/Tests/UnitTests/Calculus/AConstantExponentialBesideAFoldedLogarithmIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AConstantExponentialBesideAFoldedLogarithmIntegralTest.cs index 239ff0f28..30a39ff35 100644 --- a/Sources/Tests/UnitTests/Calculus/AConstantExponentialBesideAFoldedLogarithmIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/AConstantExponentialBesideAFoldedLogarithmIntegralTest.cs @@ -74,7 +74,10 @@ private static void DifferentiatesBack(string integrand, int longest) /// /// An even power of x, where the factorisation reads a symbol and splits the /// biquadratic over it into arms on its sign -- coth(a + 2 ln(x))/x^2 is 3,577 - /// characters in the symbol and 413 as the exponential, which is kept. + /// characters in the symbol and 413 as the exponential, which is kept. The bound was 1,300 + /// until the arctangent's poles joined the points the answer lists as having no value: + /// x^2 coth(a + 2 ln(x))^2 keeps two arctangents and went from 1,128 characters to + /// 1,382 with their two conditions. https://github.com/asc-community/AngouriMath/issues/2256 /// [Theory] [InlineData("coth(a + 2*ln(x))/x^2")] @@ -82,6 +85,6 @@ private static void DifferentiatesBack(string integrand, int longest) [InlineData("coth(a + 2*ln(x))^2")] [InlineData("x^2*coth(a + 2*ln(x))^2")] [InlineData("tanh(a + 2*ln(x))^2")] - public void AnEvenPowerOfX(string integrand) => DifferentiatesBack(integrand, 1300); + public void AnEvenPowerOfX(string integrand) => DifferentiatesBack(integrand, 1400); } } diff --git a/Sources/Tests/UnitTests/Convenience/ArctangentPolesTest.cs b/Sources/Tests/UnitTests/Convenience/ArctangentPolesTest.cs new file mode 100644 index 000000000..57af0d08f --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/ArctangentPolesTest.cs @@ -0,0 +1,51 @@ +// +// 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 AngouriMath; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Convenience +{ + /// + /// arctan and arccot are undefined at their poles, i and -i, where they evaluate to an + /// infinity, as ln(0) does and as the tangent is where its cosine is 0. Their condition said True, and + /// arctan(x) - arctan(x) simplified to 0, which has a value at i. + /// #2256 + /// + [Trait("Area", "Convenience")] + public sealed class ArctangentPolesTest + { + [Theory] + [InlineData("arctan(x)", "i", false)] + [InlineData("arctan(x)", "-i", false)] + [InlineData("arctan(x)", "2", true)] + [InlineData("arctan(x)", "0", true)] + [InlineData("arctan(x)", "1/2 + 6/5 * i", true)] + [InlineData("arccot(x)", "i", false)] + [InlineData("arccot(x)", "-i", false)] + [InlineData("arccot(x)", "0", true)] + [InlineData("arccot(x)", "-3", true)] + public void ItIsDefinedOffItsPoles(string expression, string at, bool defined) + { + var condition = expression.ToEntity().DomainCondition.Substitute("x", at.ToEntity()).Evaled; + Assert.Equal(defined ? Entity.Boolean.True : Entity.Boolean.False, condition); + var value = expression.ToEntity().Substitute("x", at.ToEntity()).Evaled; + Assert.Equal(defined, value is Entity.Number number && number.IsFinite); + } + + [Theory] + [InlineData("arctan(x) - arctan(x)")] + [InlineData("arccot(x) - arccot(x)")] + public void ASimplificationHasNoValueAtAPole(string expression) + { + var simplified = expression.ToEntity().Simplify(); + Assert.True(simplified.Substitute("x", "i".ToEntity()).Evaled.IsNaN, $"{simplified} at i"); + Assert.Equal(Entity.Number.Integer.Zero, simplified.Substitute("x", 2).Evaled); + } + } +}