From 35259e3705a32f7556beae862e5138b8c929b15f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 22 Oct 2026 02:09:56 +0000 Subject: [PATCH] Name an inverse sine of a sine of a linear as linear on each interval Rubi's 5.1.5 x/asin(sin(x)) was declined. On each interval where cos(L) keeps its sign, asin(sin(L)) for L = a + k x is s k x + c, with s = cos(L)/sqrt(cos(L)^2) and c constant there; acos(cos(L)) is the same with the sign of sin(L). Named so with s and c as symbols, the integrand is a rational function of x, and its answer is written back: a whole power of s is 1 or s, a division by s is a multiplication, and a sum equal to s k x + c, decided in lowest terms over the symbols, is the inverse sine again, so that the logarithm holds asin(sin(x)) and not s x + (asin(sin(x)) - s x). Dispatched last, so that a polynomial beside the inverse sine keeps the answer parts gives it. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 17 ++++++ .../Integration/IndefiniteIntegralSolver.cs | 58 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 4 ++ ...OfASineLinearOnEachIntervalIntegralTest.cs | 51 ++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AnInverseSineOfASineLinearOnEachIntervalIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 3adcb9293..dc7f4c50c 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -554,6 +554,23 @@ without it ([#233](https://github.com/asc-community/AngouriMath/issues/233)). |---|---|---| | `"x/(acosh(x)*sqrt(-1+x)*sqrt(1+x))".ToEntity().Integrate("x")` | `integral(...)` | 93 characters, `K Chi(acosh(x))` | +### An inverse sine of a sine of a linear is integrated as linear on each interval + +**Answers where there were none, and one that was wrong.** Rubi's 5.1.5 `x/asin(sin(x))` was declined, and so was +`asin(sin(x))/x`. 2.5.0 integrated `1/asin(sin(x))` to `ln(asin(sin(x)))`, which is wrong wherever `cos(x) < 0`, and master +declined it before this change. On each interval where `cos(x)` keeps its sign, `asin(sin(x))` is `s x + c`, with +`s = cos(x)/sqrt(cos(x)^2)` and `c` constant there, and an inverse cosine of a cosine is the same with the sign of the +sine. Named so, the integrand is a rational function of x. The answer is the generic case's on every interval, as for a +logarithm whose derivative is a constant. Asked last, so a polynomial beside the inverse sine keeps the answer parts +gives it ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x/asin(sin(x))".ToEntity().Integrate("x")` | `integral(...)` | 111 characters | +| `"1/asin(sin(x))".ToEntity().Integrate("x")` | `ln(arcsin(sin(x)))`, wrong past `x = pi/2` | 50 characters, `ln(arcsin(sin(x))) cos(x)/sqrt(cos(x)^2)` | +| `"asin(sin(x))/x".ToEntity().Integrate("x")` | `integral(...)` | 222 characters | +| `"x/acos(cos(a+b*x))".ToEntity().Integrate("x")` | `integral(...)` | 171 characters | + ### An arcoth of a linear is integrated as its artanh plus a constant **Answers where there were none.** `acoth(x)^2/(1 - x^2)^2`, Rubi's 7.4.1, was declined, where diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index a70cb8786..20d104314 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29705,6 +29705,64 @@ node is Powf(var @base, var exponent) && @base == MathS.e && powers.TryGetValue( return MathS.Piecewise(new[] { new Providedf(whereZero, intercept.EqualTo(Number.Integer.Zero)) }, generic); } + /// + /// An inverse sine of a sine of a linear L = a + k x is s k x + c on each interval + /// where cos(L) keeps its sign, with s = cos(L)/sqrt(cos(L)^2) that sign and c + /// constant there, and an inverse cosine of a cosine the same with the sign of sin(L): + /// the derivative of asin(sin(L)) is k cos(L)/sqrt(1 - sin(L)^2). Named so, with + /// s and c standing for those, x/asin(sin(x)) is the rational function + /// x/(s x + c), which the rules answer, where the inverse sine was an atom no rule read past + /// a polynomial beside it. The answer is the generic case's on every interval, as + /// 's is, and a whole power of s in + /// it is 1 or s. Real only: off the real line the sign is not constant + /// anywhere. Rubi's 5.1.5. Asked as a question of its own. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByNamingAnInverseSineOfASineLinearOnEachInterval(Entity expr, Entity.Variable x, bool integrateByParts) + { + Entity? named = null; + Entity? sign = null; + Entity? slope = null; + foreach (var node in expr.Nodes) + { + (Entity? linear, Entity? signOf) = node switch + { + Arcsinf(Sinf(var inner)) => (inner, (Entity)MathS.Cos(inner)), + Arccosf(Cosf(var inner)) => (inner, MathS.Sin(inner)), + _ => (null, null) + }; + if (linear is null || signOf is null || !linear.ContainsNode(x)) + continue; + var rate = linear.Differentiate(x).InnerSimplified; + if (rate.ContainsNode(x) || rate is Derivativef || rate == Number.Integer.Zero) + continue; + (named, sign, slope) = (node, signOf / MathS.Sqrt(MathS.Pow(signOf, 2)), rate); + break; + } + if (named is null || sign is null || slope is null) + return null; + var s = Variable.CreateUnique(expr, "s"); + var constant = Variable.CreateUnique(expr + s, "c"); + var rewritten = expr.Substitute(named, s * slope * x + constant); + if (rewritten == expr || rewritten.ContainsNode(named)) + return null; + if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } answer) + return null; + // s is 1 or -1, so a whole power of it is 1 or s, and dividing by it is multiplying by it. + Entity Folded(Entity e) => e.Replace(node => node switch + { + Powf(var @base, Number.Integer power) when @base == s => power.EInteger.IsEven ? Number.Integer.One : s, + Divf(var above, var below) when below == s => above * s, + _ => node + }).InnerSimplified; + // And a sum that is the name, `s x + c` in a logarithm, is the inverse sine again, where written + // back term by term it was `s x + (asin(sin(x)) - s x)`. + var linearInX = s * slope * x + constant; + var written = Folded(answer).Replace(node => node is Sumf or Minusf && node.ContainsNode(constant) + && Functions.PartialFractions.InLowestTermsOverTheSymbols(node - linearInX) == Number.Integer.Zero ? named : node); + return Folded(written.Substitute(constant, named - s * slope * x)).Substitute(s, sign); + } + /// /// The sign of a real-valued function of among the factors of the /// integrand is constant on every interval between its zeros, and goes in front of the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index b2acbfc9d..2568ab2cd 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1789,6 +1789,10 @@ internal static bool ASumOverRootsWouldAnswer // `(atanh(x) + K)^2/(1 - x^2)^2` with `K = acoth(x) - atanh(x)`, constant where it is // continuous, which the substitution under the hyperbolic tangent answers. if ((answer = IndefiniteIntegralSolver.SolveByWritingAnArcothAsAnArtanhPlusAConstant(expr, x, integrateByParts)) is { }) return answer; + // And an inverse sine of a sine of a linear, which is linear on each interval its cosine keeps + // a sign on. Last, so that every one answered as written keeps that answer: parts answers a + // polynomial beside it. + if ((answer = IndefiniteIntegralSolver.SolveByNamingAnInverseSineOfASineLinearOnEachInterval(expr, x, integrateByParts)) is { }) return answer; return null; } /// diff --git a/Sources/Tests/UnitTests/Calculus/AnInverseSineOfASineLinearOnEachIntervalIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnInverseSineOfASineLinearOnEachIntervalIntegralTest.cs new file mode 100644 index 000000000..e31f3173b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnInverseSineOfASineLinearOnEachIntervalIntegralTest.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 System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// An inverse sine of a sine of a linear, or an inverse cosine of a cosine, is linear on each interval + /// where the cosine, or the sine, keeps its sign: asin(sin(x)) is s x + c there, with + /// s = cos(x)/sqrt(cos(x)^2). Rubi's 5.1.5 x/asin(sin(x)) was declined, and is + /// asin(sin(x)) + (s x - asin(sin(x))) ln(asin(sin(x))) up to a constant on each interval. + /// Checked on both sides of a zero of the cosine, and of the sine. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnInverseSineOfASineLinearOnEachIntervalIntegralTest + { + [Theory] + // Rubi 5.1.5:619. + [InlineData("x/asin(sin(x))")] + [InlineData("1/asin(sin(x))")] + [InlineData("asin(sin(x))/x")] + [InlineData("x/acos(cos(a + b*x))")] + public void IsAnsweredOnEveryInterval(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + // Past pi/2 and past -pi/2 the cosine of x changes sign, and past 3.9 the sine of 0.4 + 0.7 x. + foreach (var at in new[] { -2.3, -0.9, 0.4, 1.3, 2.2, 2.9, 4.6 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}