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 @@ -390,6 +390,23 @@ polynomial exponent in `u` now, and the question asked in `u`. Rubi's 2.3
| `"F^(a + b/(c + d*x))/(c + d*x)".ToEntity().Integrate("x")` | `integral(...)` | an exponential integral |
| `"F^(a + b/(c + d*x)^3)*(c + d*x)^2".ToEntity().Integrate("x")` | `integral(...)` | an exponential integral of the cube, and the exponential |

### A function of a quotient of two linears is integrated over the quotient's denominator

**Answers where there were none.** `sin((a + b x)/(c + d x))` and its powers were declined, and so
were the cosine's, the hyperbolic sine's and cosine's and the exponential of the same quotient,
where `sin(p + k/(c + d x))` is answered in the sine and cosine integrals and `e^(p + k/(c + d x))`
in the exponential integral. The quotient is one of those: `b/d + (a d - b c)/(d (c + d x))`, by
polynomial division, for `d` and `a d - b c` not zero. Each such argument is written so and the
question asked again. Rubi's 4.7.7, 6.1.5 and 6.2.5
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sin((a + b*x)/(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | in the sine and cosine integrals of `(a d - b c)/(d (c + d x))` |
| `"cos((a + b*x)/(c + d*x))^2".ToEntity().Integrate("x")` | `integral(...)` | in the sine and cosine integrals of twice that |
| `"sinh((a + b*x)/(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | in the exponential integrals of `±(a d - b c)/(d (c + d x))` |
| `"e^((a + b*x)/(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | in the exponential integral of `(a d - b c)/(d (c + d x))` |

### A polynomial over a power of a binomial past the cube is integrated

**Answers where there were none.** `P(x)/(a + b x^n)^k` with symbols in the binomial, `n >= 3`, was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11418,6 +11418,97 @@ static bool IsEvenIn(Entity polynomial, Entity.Variable even, Entity.Variable ot
return !inPlaceholder.ContainsNode(x) && TreeAnalyzer.TryGetPolynomial(inPlaceholder, placeholder, out _);
}

/// <summary>
/// A function whose argument is a quotient of two linears, <c>(a + b x)/(c + d x)</c>, with
/// that argument written over the denominator: <c>b/d + (a d - b c)/(d (c + d x))</c>, a
/// constant plus a multiple of the reciprocal of a linear.
/// </summary>
/// <remarks>
/// <para>
/// Rubi's <c>sin((a + b x)/(c + d x))</c> and its powers were declined, and the hyperbolic
/// sine and cosine of the same, written in exponentials, likewise, where
/// <c>sin(a + k/(c + d x))</c> is answered in sines and cosine integrals and
/// <c>e^(a + k/(c + d x))</c> in exponential integrals: those rules read a reciprocal of a
/// linear, and a quotient of two linears is one plus a constant. Exact: polynomial division,
/// for <c>d</c> and <c>a d - b c</c> not zero, as everywhere in the integrator.
/// </para>
/// <para>
/// The arguments of the sine, cosine, tangent, cotangent, secant and cosecant and the
/// exponents of the exponential, each written so; the same question in another spelling.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveByWritingAQuotientOfLinearsOverItsDenominator(Entity expr, Entity.Variable x, bool integrateByParts)
{
var names = new Dictionary<Variable, Entity>();
Entity? Divided(Entity argument)
{
// Written as a quotient, or a constant times one: the sum this writes is neither,
// so the same question asked again does not divide it a second time.
Entity factor = Number.Integer.One;
var quotient = argument;
// The product's two children as written: read through, `-(a + b x)/(c + d x)` as a
// constant times a quotient is a quotient taken apart into its factors.
if (argument is Mulf(var left, var right))
(factor, quotient) = left.ContainsNode(x) ? (right, left) : (left, right);
if (factor.ContainsNode(x))
return null;
if (quotient is not Divf(var above, var below))
return null;
if (!below.ContainsNode(x) || !above.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(above, x, out var b, out var a) || b.ContainsNode(x) || a.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(below, x, out var d, out var c) || d.ContainsNode(x) || c.ContainsNode(x))
return null;
var remainder = (a * d - b * c).InnerSimplified;
if (remainder.Evaled is Number.Complex { IsZero: true } || d.Evaled is Number.Complex { IsZero: true })
return null;
// The constant and the multiple named, each a symbol of its own while the question
// is asked: `sin(b/d + ((a d - b c)/d)/(c + d x))` was declined where
// `sin(p + k/(c + d x))` is answered, the rules reading a symbol where they meet a
// quotient of symbols.
Entity Named(Entity value)
{
value = value.InnerSimplified;
if (value is Variable || value is Number)
return value;
foreach (var pair in names)
if (pair.Value == value)
return pair.Key;
var name = Variable.CreateUnique(expr + names.Keys.Aggregate((Entity)Number.Integer.Zero, (sum, v) => sum + v), "k_quotient");
names[name] = value;
return name;
}
// A constant in front stays in front, so that `e^Q` and `e^(-Q)` keep one argument
// and their product is 1.
var divided = Named(b / d) + Named(remainder / d) / below;
return factor == Number.Integer.One ? divided : factor * divided;
}
var changed = false;
var rewritten = expr.Replace(node =>
{
Entity? divided;
switch (node)
{
case Sinf(var y) when (divided = Divided(y)) is not null: changed = true; return MathS.Sin(divided);
case Cosf(var y) when (divided = Divided(y)) is not null: changed = true; return MathS.Cos(divided);
case Tanf(var y) when (divided = Divided(y)) is not null: changed = true; return MathS.Tan(divided);
case Cotanf(var y) when (divided = Divided(y)) is not null: changed = true; return MathS.Cotan(divided);
case Secantf(var y) when (divided = Divided(y)) is not null: changed = true; return MathS.Sec(divided);
case Cosecantf(var y) when (divided = Divided(y)) is not null: changed = true; return MathS.Cosec(divided);
case Powf(var @base, var exponent) when @base == MathS.e && (divided = Divided(exponent)) is not null:
changed = true;
return MathS.Pow(MathS.e, divided);
default:
return node;
}
});
if (!changed || Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts) is not { } answer)
return null;
foreach (var pair in names)
answer = answer.Substitute(pair.Key, pair.Value);
return answer;
}

/// <summary>
/// Trigonometric functions of several arguments that are whole or rational multiples of one
/// linear, <c>p + q x</c>, written in <c>u = p + q x</c>: <c>csc(a + b x) csc(2a + 2b x)^2</c>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -876,13 +876,16 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleWhereOnePlusAHyperbolicCosineIsASquare(expr, x, integrateByParts)) is { }) return answer;
// The hyperbolic sine's, off the real line: 1 + i sinh(y) is (cosh(y/2) + i sinh(y/2))^2.
if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfOnePlusAnImaginaryHyperbolicSine(expr, x, integrateByParts)) is { }) return answer;
// Several trigonometric arguments that are multiples of one linear with an offset or a
// symbolic slope, written in that linear: before the substitution search, which reads
// each function on its own.
// A quotient of linears as a function's argument, written over its denominator: a
// constant plus a multiple of the reciprocal of a linear, which the rules read.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAQuotientOfLinearsOverItsDenominator(expr, x, integrateByParts)) is { }) return answer;
// A cosine and a sine over a power of another such sum, through the denominator, its
// derivative and a constant, down to the reciprocal of the base: before the substitution
// search, which reads the quotient term by term.
if ((answer = IndefiniteIntegralSolver.SolveACosineAndASineOverAPowerOfAnother(expr, x, integrateByParts)) is { }) return answer;
// Several trigonometric arguments that are multiples of one linear with an offset or a
// symbolic slope, written in that linear: before the substitution search, which reads
// each function on its own.
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// 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, cosine or exponential of a quotient of two linears, <c>(a + b x)/(c + d x)</c>,
/// written as the constant <c>b/d</c> plus <c>(a d - b c)/d</c> over <c>c + d x</c>, and
/// answered in the sine, cosine and exponential integrals. Rubi's 4.7.7, 6.1.5 and 6.2.5.
/// <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>,
/// <c>d = 0.7</c>, on both sides of the pole at <c>c + d x = 0</c>.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class QuotientOfLinearsAsAnArgumentIntegralTest
{
[Theory]
[InlineData("sin((a + b*x)/(c + d*x))")]
[InlineData("sin((a + b*x)/(c + d*x))^2")]
[InlineData("sin((a + b*x)/(c + d*x))^3")]
[InlineData("cos((a + b*x)/(c + d*x))")]
[InlineData("cos((a + b*x)/(c + d*x))^2")]
[InlineData("sinh((a + b*x)/(c + d*x))")]
[InlineData("sinh((a + b*x)/(c + d*x))^3")]
[InlineData("cosh((a + b*x)/(c + d*x))^2")]
[InlineData("e^((a + b*x)/(c + d*x))")]
[InlineData("sin(2*(a + b*x)/(c + d*x))")]
public void IsWrittenOverTheDenominator(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).Substitute("d", 0.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.5, -2.0, 0.3, 0.8, 1.4 })
{
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