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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// 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
/// where <c>cos(L)</c> keeps its sign, with <c>s = cos(L)/sqrt(cos(L)^2)</c> that sign and <c>c</c>
/// constant there, and an inverse cosine of a cosine the same with the sign of <c>sin(L)</c>:
/// the derivative of <c>asin(sin(L))</c> is <c>k cos(L)/sqrt(1 - sin(L)^2)</c>. Named so, with
/// <c>s</c> and <c>c</c> standing for those, <c>x/asin(sin(x))</c> is the rational function
/// <c>x/(s x + c)</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
/// <see cref="SolveByNamingALogarithmLinearInTheVariable"/>'s is, and a whole power of <c>s</c> in
/// it is 1 or <c>s</c>. Real <paramref name="x"/> 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
/// </summary>
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);
}

/// <summary>
/// The sign of a real-valued function of <paramref name="x"/> among the factors of the
/// integrand is constant on every interval between its zeros, and goes in front of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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: <c>asin(sin(x))</c> is <c>s x + c</c> there, with
/// <c>s = cos(x)/sqrt(cos(x)^2)</c>. Rubi's 5.1.5 <c>x/asin(sin(x))</c> was declined, and is
/// <c>asin(sin(x)) + (s x - asin(sin(x))) ln(asin(sin(x)))</c> up to a constant on each interval.
/// Checked on both sides of a zero of the cosine, and of the sine.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[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}");
}
}
}
}
Loading