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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ after it
|---|---|---|
| `"(a*c + b*c*x)^(-3-2*p)*(f + g*x)*(a^2 + 2*a*b*x + b^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)`; an answer with no value on the unreleased master | the antiderivative |

### A sine or a cosine of a shifted argument is written in the other

**Answers where there were none.** `sin(a + b x) sec(c + b x)^3` was declined, with the rest of Rubi's 4.7.1
that puts a sine or a cosine of one linear argument beside functions of another of the same slope: nothing read
two arguments. The addition formula writes the first in the second,
`sin(a + b x) = sin(a - c) cos(c + b x) + cos(a - c) sin(c + b x)`, and the integrand is then of one argument
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sin(a + b*x)*sec(c + b*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 82 characters |
| `"cos(a + b*x)*sec(c + b*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 111 characters |
| `"sin(a + b*x)*csc(c + b*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 215 characters |

### A rational function of the secant with two sums below the bar is written in the cosine

**Answers where there were none.** `sec(x)/((a + b sec(x)) (c + d sec(x))^2)` ran past thirty seconds: the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9259,6 +9259,63 @@ private static (Entity Coefficient, Entity Degree)? TheMonomial(Entity expr, Ent
return null;
}

/// <summary>
/// The sines and cosines of one linear argument written in another of the same slope, where the
/// two differ by a constant <c>d = A - B</c>: <c>sin(A) = sin(d) cos(B) + cos(d) sin(B)</c> and
/// <c>cos(A) = cos(d) cos(B) - sin(d) sin(B)</c>, so that every trigonometric function is of
/// <c>B</c>. <c>sin(a + b x) sec(c + b x)^3</c> is <c>(sin(a - c) + cos(a - c) tan(c + b x)) sec(c + b x)^2</c>.
/// </summary>
/// <remarks>
/// Rubi's 4.7.1 has seventeen of these, each declined in a few milliseconds: nothing read two
/// arguments of which only one is a sine or a cosine. The same question in another spelling.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByWritingASineOfAShiftedArgumentInTheOther(Entity expr, Entity.Variable x, bool integrateByParts)
{
var arguments = new List<Entity>();
var onlySineOrCosine = new Dictionary<Entity, bool>();
foreach (var node in expr.Nodes)
{
if (node is not (Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf) || !node.ContainsNode(x))
continue;
var argument = node.DirectChildren.First();
if (!arguments.Contains(argument))
{
arguments.Add(argument);
onlySineOrCosine[argument] = true;
}
if (node is not (Sinf or Cosf))
onlySineOrCosine[argument] = false;
}
if (arguments.Count != 2)
return null;
var (first, second) = (arguments[0], arguments[1]);
// Each with an offset: `cos(c) cos(d x) - sin(c) sin(d x)`, the addition formula's own
// expansion of `cos(c + d x)` by another rule, is not to be written back.
if (!TreeAnalyzer.TryGetPolyLinear(first, x, out var slopeOfFirst, out var offsetOfFirst) || slopeOfFirst.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(second, x, out var slopeOfSecond, out var offsetOfSecond) || slopeOfSecond.ContainsNode(x)
|| TreeAnalyzer.IsZero(offsetOfFirst) || TreeAnalyzer.IsZero(offsetOfSecond)
|| slopeOfFirst.Expand().InnerSimplified != slopeOfSecond.Expand().InnerSimplified
&& (slopeOfFirst - slopeOfSecond).Expand().InnerSimplified.Evaled is not Number.Complex { IsZero: true })
return null;
// The one of sines and cosines only is written in the other.
var (shifted, kept) = onlySineOrCosine[first] ? (first, second) : onlySineOrCosine[second] ? (second, first) : (null!, null!);
if (shifted is null)
return null;
var d = (shifted - kept).Expand().InnerSimplified;
if (d.ContainsNode(x) || d.Evaled is Number.Complex { IsZero: true })
return null;
var rewritten = expr.Replace(node => node switch
{
Sinf(var a) when a == shifted => MathS.Sin(d) * MathS.Cos(kept) + MathS.Cos(d) * MathS.Sin(kept),
Cosf(var a) when a == shifted => MathS.Cos(d) * MathS.Cos(kept) - MathS.Sin(d) * MathS.Sin(kept),
_ => node,
});
if (rewritten.Nodes.Any(node => node is Sinf or Cosf && node.DirectChildren.First() == shifted))
return null;
return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);
}

/// <summary>
/// A product of two tangents, cotangents, secants or cosecants of linear arguments whose
/// difference or sum is a constant, written as the functions of each apart: with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// Two tangents, cotangents, secants or cosecants of arguments a constant apart, written
// as functions of each alone by the addition formulas.
if ((answer = IndefiniteIntegralSolver.SolveByWritingTwoFunctionsOfShiftedArgumentsApart(expr, x, integrateByParts)) is { }) return answer;
// A sine or a cosine of one argument beside functions of another shifted from it: the addition
// formula writes it in the other.
if ((answer = IndefiniteIntegralSolver.SolveByWritingASineOfAShiftedArgumentInTheOther(expr, x, integrateByParts)) is { }) return answer;
// A constant out of a fractional power of a trigonometric factor: `sqrt(b sec(x))` is
// `sqrt(b) sqrt(sec(x))` for a positive `b`, which meets the other powers of the
// secant beside it. After the rules that answer the same shapes for any real
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// 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>
/// A sine or a cosine of <c>a + b x</c> beside functions of <c>c + b x</c>, written in the latter by the
/// addition formula: <c>sin(a + b x) = sin(a - c) cos(c + b x) + cos(a - c) sin(c + b x)</c>. Rubi's 4.7.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ASineOfAShiftedArgumentIntegralTest
{
[Theory]
[InlineData("sin(a + b*x)*sec(c + b*x)^3")]
[InlineData("cos(a + b*x)*sec(c + b*x)^2")]
[InlineData("sin(a + b*x)*csc(c + b*x)^4")]
public void InTheOtherArgument(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading