diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 6faf6c53c..1db61d4ea 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -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
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 33b6f88f1..a0bcb9070 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -11418,6 +11418,97 @@ static bool IsEvenIn(Entity polynomial, Entity.Variable even, Entity.Variable ot
return !inPlaceholder.ContainsNode(x) && TreeAnalyzer.TryGetPolynomial(inPlaceholder, placeholder, out _);
}
+ ///
+ /// A function whose argument is a quotient of two linears, (a + b x)/(c + d x), with
+ /// that argument written over the denominator: b/d + (a d - b c)/(d (c + d x)), a
+ /// constant plus a multiple of the reciprocal of a linear.
+ ///
+ ///
+ ///
+ /// Rubi's sin((a + b x)/(c + d x)) and its powers were declined, and the hyperbolic
+ /// sine and cosine of the same, written in exponentials, likewise, where
+ /// sin(a + k/(c + d x)) is answered in sines and cosine integrals and
+ /// e^(a + k/(c + d x)) 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 d and a d - b c not zero, as everywhere in the integrator.
+ ///
+ ///
+ /// 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
+ ///
+ ///
+ internal static Entity? SolveByWritingAQuotientOfLinearsOverItsDenominator(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ var names = new Dictionary();
+ 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;
+ }
+
///
/// Trigonometric functions of several arguments that are whole or rational multiples of one
/// linear, p + q x, written in u = p + q x: csc(a + b x) csc(2a + 2b x)^2
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 364c40dfc..91f5c832d 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -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.
diff --git a/Sources/Tests/UnitTests/Calculus/QuotientOfLinearsAsAnArgumentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/QuotientOfLinearsAsAnArgumentIntegralTest.cs
new file mode 100644
index 000000000..24f73c174
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/QuotientOfLinearsAsAnArgumentIntegralTest.cs
@@ -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
+{
+ ///
+ /// A sine, cosine or exponential of a quotient of two linears, (a + b x)/(c + d x),
+ /// written as the constant b/d plus (a d - b c)/d over c + d x, and
+ /// answered in the sine, cosine and exponential integrals. Rubi's 4.7.7, 6.1.5 and 6.2.5.
+ /// #718
+ ///
+ ///
+ /// Checked by differentiating back with a = 0.3, b = 0.9, c = 1.1,
+ /// d = 0.7, on both sides of the pole at c + d x = 0.
+ ///
+ [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}");
+ }
+ }
+ }
+}