From d4e5b61ec0e6fa34a668b8b8b3b6894b7b4351e4 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 16:07:42 +0000 Subject: [PATCH 1/2] A power of csc - sin or sec - cos is integrated as sine and cosine powers Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 ++ .../Integration/IndefiniteIntegralSolver.cs | 128 +++++++++++++++--- .../Integration/Integration.Definition.cs | 3 + ...erenceOfReciprocalFunctionsIntegralTest.cs | 51 +++++++ 4 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..82b519b99 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -523,6 +523,20 @@ antiderivative on each side of it, as Rubi's is | `"1/((2 + 3*x^2)^(1/4)*(4 + 3*x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same in `(2 + 3 x^2)^(1/4)` | | `"1/((-2 + 3*x^2)*(-1 + 3*x^2)^(1/4))".ToEntity().Integrate("x")` | `integral(...)` | `-(arctan(u) + artanh(u))/(2 sqrt(6))`, `u = sqrt(3) x/(sqrt(2) (-1 + 3 x^2)^(1/4))` | +### A power of `csc(x) - sin(x)` or `sec(x) - cos(x)` is integrated as sine and cosine powers + +**Answers where there were none.** `(csc(x) - sin(x))^(5/2)` was declined, with the rest of Rubi's +4.7.7 to a power that is not whole of either difference, either way up. `csc(z) - sin(z)` is +`cos(z)^2/sin(z)` and `sec(z) - cos(z)` is `sin(z)^2/cos(z)`; written so, the power is a product of +powers of the sine and cosine, which are integrated through the tangent, a quotient of whole powers +of the six functions read inside a power as it is +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(csc(x) - sin(x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | powers of `sin(x)` times `(cos(x)^2/sin(x))^(5/2)` | +| `"1/(sec(x) - cos(x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | a root of `cos(x)` with a logarithm and an arctangent, times `(sin(x)^2/cos(x))^(-3/2)` | + ### A product of powers of the trigonometric functions, some not whole, is integrated as sine and cosine powers **Answers where there were none.** `(a sin(x))^(5/2) sqrt(b sec(x))` was declined, and so were diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 875a31f1c..3f5eb8621 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -8854,33 +8854,36 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( var (@base, power) = factor is Powf(var b, var p) && p.Evaled is Number.Rational r ? (b, r) : (factor, (Number.Rational)Number.Integer.One); - // A constant multiple of a function, and a whole power of one inside the power: - // `(b tan(z)^3)^(3/2)`. - if (@base is Mulf(var left, var right)) - @base = !left.ContainsNode(x) ? right : !right.ContainsNode(x) ? left : @base; - if (@base is Powf(var inner, Number.Integer innerPower)) - (@base, power) = (inner, (Number.Rational)(power * innerPower)); if (underneath) power = (Number.Rational)(-power); if (power is not Number.Integer) notWhole = true; - // The powers of the sine and of the cosine each function is. - (Entity Of, int OnSine, int OnCosine)? read = @base switch - { - Sinf(var of) => (of, 1, 0), - Cosf(var of) => (of, 0, 1), - Tanf(var of) => (of, 1, -1), - Cotanf(var of) => (of, -1, 1), - Secantf(var of) => (of, 0, -1), - Cosecantf(var of) => (of, -1, 0), - _ => null - }; - if (read is null || argument is not null && argument != read.Value.Of) + // Inside the power, a constant multiple of a product and quotient of whole powers of + // the functions: `(b tan(z)^3)^(3/2)`, and `(cos(z)^2/sin(z))^(5/2)`, which is how + // `(csc(z) - sin(z))^(5/2)` is written. + if (!TryReadAMonomialInTheTrigonometricFunctions(@base, x, out var parts)) return null; - var (functionOf, onSine, onCosine) = read.Value; - argument = functionOf; - sine = (Number.Rational)(sine + power * onSine); - cosine = (Number.Rational)(cosine + power * onCosine); + foreach (var (function, times) in parts) + { + // The powers of the sine and of the cosine each function is. + (Entity Of, int OnSine, int OnCosine)? read = function switch + { + Sinf(var of) => (of, 1, 0), + Cosf(var of) => (of, 0, 1), + Tanf(var of) => (of, 1, -1), + Cotanf(var of) => (of, -1, 1), + Secantf(var of) => (of, 0, -1), + Cosecantf(var of) => (of, -1, 0), + _ => null + }; + if (read is null || argument is not null && argument != read.Value.Of) + return null; + var (functionOf, onSine, onCosine) = read.Value; + argument = functionOf; + var weight = (Number.Rational)(power * Number.Integer.Create(times)); + sine = (Number.Rational)(sine + weight * onSine); + cosine = (Number.Rational)(cosine + weight * onCosine); + } } if (argument is null || !notWhole || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) @@ -8914,6 +8917,87 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( } + /// + /// A difference of a trigonometric function and its reciprocal's partner written as the + /// quotient it is: csc(z) - sin(z) is cos(z)^2/sin(z) and sec(z) - cos(z) + /// is sin(z)^2/cos(z), so that a power of either is a product of powers of the sine + /// and cosine; the integrand so written is asked as the same question. + /// + /// + /// (csc(x) - sin(x))^(5/2) was declined, with the rest of Rubi's 4.7.7 to a power that + /// is not whole of either difference, either way up. As the quotient it is a product of + /// powers of the sine and cosine, which the rule beside it reads through the tangent. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByWritingADifferenceOfReciprocalFunctionsAsAQuotient(Entity expr, Entity.Variable x, bool integrateByParts) + { + var rewritten = expr.Replace(node => + { + if (!node.ContainsNode(x)) + return node; + // Each term a signed function: `csc(z)`, `-sin(z)`, `-1 * cos(z)`. + (Entity Function, int Sign) Signed(Entity term, int sign) => term switch + { + Mulf(Number.Integer { EInteger: var one }, var f) when one.Equals(EInteger.FromInt32(-1)) => (f, -sign), + Mulf(var f, Number.Integer { EInteger: var one }) when one.Equals(EInteger.FromInt32(-1)) => (f, -sign), + _ => (term, sign) + }; + List<(Entity Function, int Sign)>? signed = node switch + { + Minusf(var minuend, var subtrahend) => new() { Signed(minuend, 1), Signed(subtrahend, -1) }, + Sumf(var left, var right) when left is not (Sumf or Minusf) && right is not (Sumf or Minusf) => new() { Signed(left, 1), Signed(right, 1) }, + _ => null + }; + if (signed is null) + return node; + var (first, second) = (signed[0], signed[1]); + if (first.Sign == second.Sign) + return node; + var (positive, negative) = first.Sign > 0 ? (first.Function, second.Function) : (second.Function, first.Function); + return (positive, negative) switch + { + (Cosecantf(var a), Sinf(var b)) when a == b => MathS.Pow(MathS.Cos(a), 2) / MathS.Sin(a), + (Sinf(var a), Cosecantf(var b)) when a == b => -MathS.Pow(MathS.Cos(a), 2) / MathS.Sin(a), + (Secantf(var a), Cosf(var b)) when a == b => MathS.Pow(MathS.Sin(a), 2) / MathS.Cos(a), + (Cosf(var a), Secantf(var b)) when a == b => -MathS.Pow(MathS.Sin(a), 2) / MathS.Cos(a), + _ => node + }; + }); + return rewritten == expr ? null : Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); + } + + /// + /// read as a constant times a product of whole powers of the six + /// trigonometric functions, each with how many times it divides or multiplies; false where + /// anything else holds . + /// + private static bool TryReadAMonomialInTheTrigonometricFunctions(Entity expr, Entity.Variable x, out List<(Entity Function, int Times)> parts) + { + var read = new List<(Entity Function, int Times)>(); + parts = read; + return Read(expr, 1); + + bool Read(Entity e, int times) + { + switch (e) + { + case var constant when !constant.ContainsNode(x): + return true; + case Mulf(var left, var right): + return Read(left, times) && Read(right, times); + case Divf(var above, var below): + return Read(above, times) && Read(below, -times); + case Powf(var inner, Number.Integer { EInteger: var power }) when power.CanFitInInt32(): + return Read(inner, times * power.ToInt32Unchecked()); + case Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf: + read.Add((e, times)); + return true; + default: + return false; + } + } + } + /// /// with a whole power of a product read as the product of the /// powers, a negative one on the other side of the bar: 1/(cos(x)^(7/2) sqrt(sin(x))) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 15f973f6b..09817c63c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -904,6 +904,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // A product of powers of the trigonometric functions of one argument, some not whole, // whose powers of the sine and cosine add up to an even number: the tangent's form // times a constant, which the substitution below reads. + // A difference of a function and its reciprocal's partner, `csc(z) - sin(z)`, as the + // quotient of powers it is, for the rule below. + if ((answer = IndefiniteIntegralSolver.SolveByWritingADifferenceOfReciprocalFunctionsAsAQuotient(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveAProductOfPowersOfTrigonometricFunctionsThroughTheTangent(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer; // A root of a quadratic in the tangent with a linear term, rotated until it has diff --git a/Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs new file mode 100644 index 000000000..42d5ada2c --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs @@ -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 +{ + /// + /// A power that is not whole of csc(x) - sin(x) or sec(x) - cos(x), which are + /// cos(x)^2/sin(x) and sin(x)^2/cos(x): written so, a product of powers of the + /// sine and cosine, read through the tangent. Rubi's 4.7.7. + /// #718 + /// + /// + /// Compared as complex numbers on both sides of the zeros of the sine and the cosine, where the + /// integrands are complex on some of the intervals. + /// + [Trait("Area", "Calculus")] + public sealed class DifferenceOfReciprocalFunctionsIntegralTest + { + [Theory] + [InlineData("(csc(x) - sin(x))^(5/2)")] + [InlineData("(csc(x) - sin(x))^(3/2)")] + [InlineData("1/(csc(x) - sin(x))^(1/2)")] + [InlineData("1/(csc(x) - sin(x))^(7/2)")] + [InlineData("(sec(x) - cos(x))^(5/2)")] + [InlineData("1/(sec(x) - cos(x))^(3/2)")] + [InlineData("(sin(x) - csc(x))^(3/2)")] + public void IsAProductOfPowersOfTheSineAndCosine(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + foreach (var at in new[] { -2.3, -1.1, -0.4, 0.4, 1.1, 1.9, 2.6 }) + { + 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}"); + } + } + } +} From 521d6531ec2a9b19d00dd52712667ca931cb596e Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 20:34:41 +0000 Subject: [PATCH 2/2] A product of trigonometric powers is not whole only where some function's power is not `(a csc(z)^2)^(-7/2)` is a whole power of the modulus of the cosecant, which the rule for an even power under a root answers with its sign; reading the outer power alone, the product rule took it first. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 8 +++++--- .../Continuous/Integration/Integration.Definition.cs | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 3f5eb8621..5734a6bbb 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -8856,11 +8856,11 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( : (factor, (Number.Rational)Number.Integer.One); if (underneath) power = (Number.Rational)(-power); - if (power is not Number.Integer) - notWhole = true; // Inside the power, a constant multiple of a product and quotient of whole powers of // the functions: `(b tan(z)^3)^(3/2)`, and `(cos(z)^2/sin(z))^(5/2)`, which is how - // `(csc(z) - sin(z))^(5/2)` is written. + // `(csc(z) - sin(z))^(5/2)` is written. Not whole where some function's power is not: + // `(a csc(z)^2)^(-7/2)` is a whole power of the modulus of the cosecant, which the rule for + // an even power under a root answers with its sign. if (!TryReadAMonomialInTheTrigonometricFunctions(@base, x, out var parts)) return null; foreach (var (function, times) in parts) @@ -8881,6 +8881,8 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( var (functionOf, onSine, onCosine) = read.Value; argument = functionOf; var weight = (Number.Rational)(power * Number.Integer.Create(times)); + if (weight is not Number.Integer) + notWhole = true; sine = (Number.Rational)(sine + weight * onSine); cosine = (Number.Rational)(cosine + weight * onCosine); } diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 09817c63c..2c7e9aef8 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -901,12 +901,12 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // A power of the cotangent that is not whole beside the tangent, written as the // tangent's with the constant that takes in front, which the substitution below reads. if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheCotangentInTheTangent(expr, x, integrateByParts)) is { }) return answer; + // A difference of a function and its reciprocal's partner, `csc(z) - sin(z)`, as the + // quotient of powers it is, for the rule after it. + if ((answer = IndefiniteIntegralSolver.SolveByWritingADifferenceOfReciprocalFunctionsAsAQuotient(expr, x, integrateByParts)) is { }) return answer; // A product of powers of the trigonometric functions of one argument, some not whole, // whose powers of the sine and cosine add up to an even number: the tangent's form // times a constant, which the substitution below reads. - // A difference of a function and its reciprocal's partner, `csc(z) - sin(z)`, as the - // quotient of powers it is, for the rule below. - if ((answer = IndefiniteIntegralSolver.SolveByWritingADifferenceOfReciprocalFunctionsAsAQuotient(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveAProductOfPowersOfTrigonometricFunctionsThroughTheTangent(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer; // A root of a quadratic in the tangent with a linear term, rotated until it has