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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,19 @@ read them as nonzero. They are now decided over one bar, expanded, and at pinned
| `"e^(3*acoth(a*x))/(c-a*c*x)^3".Integrate("x")`, and over `(c - a c x)^4` | left unevaluated | an antiderivative in `sqrt((a x + 1)/(a x - 1))` |
| `"e^(2*acoth(a*x))*sqrt(c-a*c*x)/x".Integrate("x")`, and over `x^2` | left unevaluated | an antiderivative in `sqrt(c - a c x)`, by cases on the sign of `c` |

### Two tangents of arguments a constant apart are written apart

**Answers where there were none.** `tan(a + b x) tan(c + b x)` was declined, with the secants,
cotangents and cosecants the same way and the products whose arguments add to a constant, Rubi's
4.7.7. By the addition formulas each such product is the functions of the two arguments apart:
`tan(A) tan(B) = cot(A - B) (tan(A) - tan(B)) - 1` and its kin, for `A - B` or `A + B` a constant
that is not a multiple of `pi` ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"tan(a + b*x)*tan(c + b*x)".ToEntity().Integrate("x")` | `integral(...)` | `cot(a - c) (ln(cos(c + b x)) - ln(cos(a + b x)))/b - x` |
| `"sec(c - b*x)*sec(a + b*x)".ToEntity().Integrate("x")` | `integral(...)` | `csc(a + c) (ln(cos(c - b x)) - ln(cos(a + b x)))/b` |

### A trigonometric function of an imaginary multiple of a logarithm is integrated in exponentials

**Answers where there were none.** `tan(a + i ln(x))` and `sin(a + ln(c x^2) sqrt(-1/4))` were
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8809,6 +8809,76 @@ private static (Entity Coefficient, Entity Degree)? TheMonomial(Entity expr, Ent
return null;
}

/// <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
/// <c>d = A - B</c>, <c>tan(A) tan(B) = cot(d) (tan(A) - tan(B)) - 1</c>.
/// </summary>
/// <remarks>
/// <para>
/// The addition formulas, read for the product: for a constant <c>d = A - B</c>,
/// <c>tan(A) tan(B) = cot(d) (tan(A) - tan(B)) - 1</c>,
/// <c>cot(A) cot(B) = cot(d) (cot(B) - cot(A)) - 1</c>,
/// <c>sec(A) sec(B) = csc(d) (tan(A) - tan(B))</c> and
/// <c>csc(A) csc(B) = csc(d) (cot(B) - cot(A))</c>; for a constant <c>s = A + B</c>,
/// <c>tan(A) tan(B) = 1 - cot(s) (tan(A) + tan(B))</c>,
/// <c>cot(A) cot(B) = 1 + cot(s) (cot(A) + cot(B))</c>,
/// <c>sec(A) sec(B) = csc(s) (tan(A) + tan(B))</c> and
/// <c>csc(A) csc(B) = csc(s) (cot(A) + cot(B))</c>. Each holds wherever both sides are
/// defined, for <c>d</c> or <c>s</c> not a multiple of <c>pi</c>, which a symbolic one is
/// taken not to be, as everywhere in this integrator. Rubi's 4.7.7 has
/// <c>tan(a + b x) tan(c + b x)</c> and the rest, and nothing read two arguments.
/// </para>
/// <para>
/// The same question in another spelling, so asked as it. A constant times the product
/// only, and the two functions of one kind.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveByWritingTwoFunctionsOfShiftedArgumentsApart(Entity expr, Entity.Variable x, bool integrateByParts)
{
Entity constant = Number.Integer.One;
Entity? first = null, second = null;
foreach (var factor in Mulf.LinearChildren(expr))
{
if (!factor.ContainsNode(x))
constant = constant == Number.Integer.One ? factor : constant * factor;
else if (first is null)
first = factor;
else if (second is null)
second = factor;
else
return null;
}
if (first is null || second is null || first.GetType() != second.GetType()
|| first is not (Tanf or Cotanf or Secantf or Cosecantf))
return null;
var a = first.DirectChildren.First();
var b = second.DirectChildren.First();
if (a == b || !TreeAnalyzer.TryGetPolyLinear(a, x, out var slopeOfA, out _) || slopeOfA.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(b, x, out var slopeOfB, out _) || slopeOfB.ContainsNode(x))
return null;
static bool IsZero(Entity value) => value.Expand().InnerSimplified.Evaled is Number.Complex { IsZero: true };
var sameSlope = IsZero(slopeOfA - slopeOfB);
if (!sameSlope && !IsZero(slopeOfA + slopeOfB))
return null;
var shift = (sameSlope ? a - b : a + b).Expand().InnerSimplified;
if (shift.ContainsNode(x) || shift.Evaled is Number.Complex { IsZero: true })
return null;
Entity apart = first switch
{
Tanf => sameSlope
? MathS.Cotan(shift) * (MathS.Tan(a) - MathS.Tan(b)) - 1
: 1 - MathS.Cotan(shift) * (MathS.Tan(a) + MathS.Tan(b)),
Cotanf => sameSlope
? MathS.Cotan(shift) * (MathS.Cotan(b) - MathS.Cotan(a)) - 1
: 1 + MathS.Cotan(shift) * (MathS.Cotan(a) + MathS.Cotan(b)),
Secantf => MathS.Cosec(shift) * (sameSlope ? MathS.Tan(a) - MathS.Tan(b) : MathS.Tan(a) + MathS.Tan(b)),
_ => MathS.Cosec(shift) * (sameSlope ? MathS.Cotan(b) - MathS.Cotan(a) : MathS.Cotan(a) + MathS.Cotan(b)),
};
return Integration.ComputeAsTheSameQuestion(constant == Number.Integer.One ? apart : constant * apart, x, integrateByParts);
}

/// <summary>
/// A quotient of two <b>homogeneous</b> polynomials in <c>sin(u)</c> and <c>cos(u)</c>,
/// integrated by <c>t = tan(u)</c> — which turns it into a rational function of <c>t</c>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// search, which reads the quotient term by term.
if ((answer = IndefiniteIntegralSolver.SolveACosineAndASineOverAPowerOfAnother(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingMultiplesOfOneLinearArgument(expr, x, integrateByParts)) is { }) return answer;
// 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 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,52 @@
//
// 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>
/// Two tangents, cotangents, secants or cosecants of arguments whose difference or sum is a
/// constant, written as the functions of each apart by the addition formulas. Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back with <c>a = 0.3</c>, <c>b = 0.9</c>, <c>c = 1.1</c>, on
/// both sides of zero and away from the poles of every function of the three arguments.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class TwoFunctionsOfShiftedArgumentsIntegralTest
{
[Theory]
[InlineData("tan(a + b*x)*tan(c + b*x)")]
[InlineData("tan(c - b*x)*tan(a + b*x)")]
[InlineData("cot(a + b*x)*cot(c + b*x)")]
[InlineData("cot(c - b*x)*cot(a + b*x)")]
[InlineData("sec(a + b*x)*sec(c + b*x)")]
[InlineData("sec(c - b*x)*sec(a + b*x)")]
[InlineData("csc(a + b*x)*csc(c + b*x)")]
[InlineData("csc(c - b*x)*csc(a + b*x)")]
public void IsWrittenApartByTheAdditionFormulas(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 0.3).Substitute("b", 0.9).Substitute("c", 1.1);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.0, -0.8, 0.1, 0.2, 0.9, 1.0 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
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