Skip to content

Commit 7d1081e

Browse files
Name an inverse sine of a sine of a linear as linear on each interval (#2048)
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. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 79754e7 commit 7d1081e

4 files changed

Lines changed: 130 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,23 @@ without it ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
587587
|---|---|---|
588588
| `"x/(acosh(x)*sqrt(-1+x)*sqrt(1+x))".ToEntity().Integrate("x")` | `integral(...)` | 93 characters, `K Chi(acosh(x))` |
589589

590+
### An inverse sine of a sine of a linear is integrated as linear on each interval
591+
592+
**Answers where there were none, and one that was wrong.** Rubi's 5.1.5 `x/asin(sin(x))` was declined, and so was
593+
`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
594+
declined it before this change. On each interval where `cos(x)` keeps its sign, `asin(sin(x))` is `s x + c`, with
595+
`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
596+
sine. Named so, the integrand is a rational function of x. The answer is the generic case's on every interval, as for a
597+
logarithm whose derivative is a constant. Asked last, so a polynomial beside the inverse sine keeps the answer parts
598+
gives it ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
599+
600+
| Input | Was (2.5.0) | Now |
601+
|---|---|---|
602+
| `"x/asin(sin(x))".ToEntity().Integrate("x")` | `integral(...)` | 111 characters |
603+
| `"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)` |
604+
| `"asin(sin(x))/x".ToEntity().Integrate("x")` | `integral(...)` | 222 characters |
605+
| `"x/acos(cos(a+b*x))".ToEntity().Integrate("x")` | `integral(...)` | 171 characters |
606+
590607
### An arcoth of a linear is integrated as its artanh plus a constant
591608

592609
**Answers where there were none.** `acoth(x)^2/(1 - x^2)^2`, Rubi's 7.4.1, was declined, where

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29710,6 +29710,64 @@ node is Powf(var @base, var exponent) && @base == MathS.e && powers.TryGetValue(
2971029710
return MathS.Piecewise(new[] { new Providedf(whereZero, intercept.EqualTo(Number.Integer.Zero)) }, generic);
2971129711
}
2971229712

29713+
/// <summary>
29714+
/// An inverse sine of a sine of a linear <c>L = a + k x</c> is <c>s k x + c</c> on each interval
29715+
/// where <c>cos(L)</c> keeps its sign, with <c>s = cos(L)/sqrt(cos(L)^2)</c> that sign and <c>c</c>
29716+
/// constant there, and an inverse cosine of a cosine the same with the sign of <c>sin(L)</c>:
29717+
/// the derivative of <c>asin(sin(L))</c> is <c>k cos(L)/sqrt(1 - sin(L)^2)</c>. Named so, with
29718+
/// <c>s</c> and <c>c</c> standing for those, <c>x/asin(sin(x))</c> is the rational function
29719+
/// <c>x/(s x + c)</c>, which the rules answer, where the inverse sine was an atom no rule read past
29720+
/// a polynomial beside it. The answer is the generic case's on every interval, as
29721+
/// <see cref="SolveByNamingALogarithmLinearInTheVariable"/>'s is, and a whole power of <c>s</c> in
29722+
/// it is 1 or <c>s</c>. Real <paramref name="x"/> only: off the real line the sign is not constant
29723+
/// anywhere. Rubi's 5.1.5. Asked as a question of its own.
29724+
/// https://github.com/asc-community/AngouriMath/issues/718
29725+
/// </summary>
29726+
internal static Entity? SolveByNamingAnInverseSineOfASineLinearOnEachInterval(Entity expr, Entity.Variable x, bool integrateByParts)
29727+
{
29728+
Entity? named = null;
29729+
Entity? sign = null;
29730+
Entity? slope = null;
29731+
foreach (var node in expr.Nodes)
29732+
{
29733+
(Entity? linear, Entity? signOf) = node switch
29734+
{
29735+
Arcsinf(Sinf(var inner)) => (inner, (Entity)MathS.Cos(inner)),
29736+
Arccosf(Cosf(var inner)) => (inner, MathS.Sin(inner)),
29737+
_ => (null, null)
29738+
};
29739+
if (linear is null || signOf is null || !linear.ContainsNode(x))
29740+
continue;
29741+
var rate = linear.Differentiate(x).InnerSimplified;
29742+
if (rate.ContainsNode(x) || rate is Derivativef || rate == Number.Integer.Zero)
29743+
continue;
29744+
(named, sign, slope) = (node, signOf / MathS.Sqrt(MathS.Pow(signOf, 2)), rate);
29745+
break;
29746+
}
29747+
if (named is null || sign is null || slope is null)
29748+
return null;
29749+
var s = Variable.CreateUnique(expr, "s");
29750+
var constant = Variable.CreateUnique(expr + s, "c");
29751+
var rewritten = expr.Substitute(named, s * slope * x + constant);
29752+
if (rewritten == expr || rewritten.ContainsNode(named))
29753+
return null;
29754+
if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } answer)
29755+
return null;
29756+
// s is 1 or -1, so a whole power of it is 1 or s, and dividing by it is multiplying by it.
29757+
Entity Folded(Entity e) => e.Replace(node => node switch
29758+
{
29759+
Powf(var @base, Number.Integer power) when @base == s => power.EInteger.IsEven ? Number.Integer.One : s,
29760+
Divf(var above, var below) when below == s => above * s,
29761+
_ => node
29762+
}).InnerSimplified;
29763+
// And a sum that is the name, `s x + c` in a logarithm, is the inverse sine again, where written
29764+
// back term by term it was `s x + (asin(sin(x)) - s x)`.
29765+
var linearInX = s * slope * x + constant;
29766+
var written = Folded(answer).Replace(node => node is Sumf or Minusf && node.ContainsNode(constant)
29767+
&& Functions.PartialFractions.InLowestTermsOverTheSymbols(node - linearInX) == Number.Integer.Zero ? named : node);
29768+
return Folded(written.Substitute(constant, named - s * slope * x)).Substitute(s, sign);
29769+
}
29770+
2971329771
/// <summary>
2971429772
/// The sign of a real-valued function of <paramref name="x"/> among the factors of the
2971529773
/// integrand is constant on every interval between its zeros, and goes in front of the

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,10 @@ internal static bool ASumOverRootsWouldAnswer
17921792
// `(atanh(x) + K)^2/(1 - x^2)^2` with `K = acoth(x) - atanh(x)`, constant where it is
17931793
// continuous, which the substitution under the hyperbolic tangent answers.
17941794
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnArcothAsAnArtanhPlusAConstant(expr, x, integrateByParts)) is { }) return answer;
1795+
// And an inverse sine of a sine of a linear, which is linear on each interval its cosine keeps
1796+
// a sign on. Last, so that every one answered as written keeps that answer: parts answers a
1797+
// polynomial beside it.
1798+
if ((answer = IndefiniteIntegralSolver.SolveByNamingAnInverseSineOfASineLinearOnEachInterval(expr, x, integrateByParts)) is { }) return answer;
17951799
return null;
17961800
}
17971801
/// <summary>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// An inverse sine of a sine of a linear, or an inverse cosine of a cosine, is linear on each interval
16+
/// where the cosine, or the sine, keeps its sign: <c>asin(sin(x))</c> is <c>s x + c</c> there, with
17+
/// <c>s = cos(x)/sqrt(cos(x)^2)</c>. Rubi's 5.1.5 <c>x/asin(sin(x))</c> was declined, and is
18+
/// <c>asin(sin(x)) + (s x - asin(sin(x))) ln(asin(sin(x)))</c> up to a constant on each interval.
19+
/// Checked on both sides of a zero of the cosine, and of the sine.
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
21+
/// </summary>
22+
[Trait("Area", "Calculus")]
23+
public sealed class AnInverseSineOfASineLinearOnEachIntervalIntegralTest
24+
{
25+
[Theory]
26+
// Rubi 5.1.5:619.
27+
[InlineData("x/asin(sin(x))")]
28+
[InlineData("1/asin(sin(x))")]
29+
[InlineData("asin(sin(x))/x")]
30+
[InlineData("x/acos(cos(a + b*x))")]
31+
public void IsAnsweredOnEveryInterval(string integrand)
32+
{
33+
var integral = integrand.ToEntity().Integrate("x");
34+
var text = integral.Stringize();
35+
Assert.DoesNotContain("integral(", text);
36+
Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 0.7);
37+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
38+
var original = Pinned(integrand.ToEntity());
39+
// 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.
40+
foreach (var at in new[] { -2.3, -0.9, 0.4, 1.3, 2.2, 2.9, 4.6 })
41+
{
42+
var want = original.Substitute("x", at).EvalNumerical();
43+
var got = derivative.Substitute("x", at).EvalNumerical();
44+
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
45+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
46+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
47+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)