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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,21 @@ roots, `1 - x^2`, is taken as its two ([#718](https://github.com/asc-community/A
| `"1/((1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,600 characters |
| `"1/(x*(1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,637 characters |

### A hyperbolic quotient over a quadratic in its sine or cosine is asked as the sine's, before the tangent

**Answers where there were none, and one longer spelling.** Rubi's 6.7.1 `sinh(x)^2/(a + b sinh(x) + c sinh(x)^2)`
and `sinh(x)^3/(a + b sinh(x) + c sinh(x)^2)` ran past the budget. The tangent of the half argument made each a
quartic with symbols in it, and its partial fractions did not return. Under Osborn's rule each is a rational
function of a sine over a symbolic quadratic in it, and the sine's rule for that shape is now asked first, before
the tangent. A quotient master answered some other way can come back spelled through the sine instead
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sinh(x)^2/(a+b*sinh(x)+c*sinh(x)^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 681 characters |
| `"sinh(x)^3/(a+b*sinh(x)+c*sinh(x)^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 810 characters |
| `"cosh(x)^2/(a+b*cosh(x)+c*cosh(x)^2)".ToEntity().Integrate("x")` | no answer in twenty seconds; 791 characters on the unreleased master | 813 characters |

### A hyperbolic integrand is asked as a trigonometric one

**Answers where there were none.** The parser writes the hyperbolic functions as exponentials, so the trigonometric
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1429,7 +1429,8 @@ static bool APowerOfAQuotientOfLinearsVanishingAtPlusOrMinusOne(Entity inU, Vari
/// the bar of degree above two: the second pass answers it in <c>e^L</c>, and the question in
/// <c>s</c> took seconds to decline, five gigabytes for <c>1/(1 + sinh(x)^3)</c> against 0.2.
/// </remarks>
internal static Entity? SolveAHyperbolicIntegrandAsATrigonometricOne(Entity expr, Entity.Variable x, bool integrateByParts, bool rationalToo)
internal static Entity? SolveAHyperbolicIntegrandAsATrigonometricOne(Entity expr, Entity.Variable x, bool integrateByParts, bool rationalToo,
bool overAQuadraticInTheSine = false)
{
// Only the question asked: a question in s is not cheap to decline, and below the top every
// sub-question a route gives up on would ask it.
Expand Down Expand Up @@ -1497,7 +1498,11 @@ static bool APowerOfAQuotientOfLinearsVanishingAtPlusOrMinusOne(Entity inU, Vari
&& AFactorAboveAQuadraticInS(inS, s))
return null;
var question = MathS.i / slopeOfL * inS;
if (Integration.ComputeAsAQuestionOfItsOwn(question, s, integrateByParts) is not { } inTermsOfS
// Or the one rule for a rational function of a sine or a cosine over a symbolic quadratic in
// it, asked ahead of the exponential substitution and cheap to decline.
if ((overAQuadraticInTheSine
? SolveARationalFunctionOfASineOverASymbolicQuadratic(inS, s, integrateByParts) is { } overTheQuadratic ? MathS.i / slopeOfL * overTheQuadratic : null
: Integration.ComputeAsAQuestionOfItsOwn(question, s, integrateByParts)) is not { } inTermsOfS
|| inTermsOfS.Nodes.Any(node => node is Integralf || node == MathS.NaN))
return null;
var linear = interceptOfL.Evaled is Number.Complex { IsZero: true } ? slopeOfL * x : interceptOfL + slopeOfL * x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,11 @@ internal static bool ASumOverRootsWouldAnswer
// `cosh(x)^7/(a + b cosh(x)^2)` and `sech(x)^3/(a + b sinh(x))^2` are a second here
// and past the budget there.
if ((answer = IndefiniteIntegralSolver.SolveByBiochesOddHyperbolicSubstitution(expr, x, integrateByParts)) is { }) return answer;
// A rational function of e^L that Osborn's rule makes one of a sine or a cosine over a symbolic
// quadratic in it, asked as that before the tangent: under it Rubi's
// `sinh(x)^2/(a + b sinh(x) + c sinh(x)^2)` is a quartic with symbols, and its partial
// fractions ran past the budget, where the sine's rule answers the question in s at once.
if ((answer = IndefiniteIntegralSolver.SolveAHyperbolicIntegrandAsATrigonometricOne(expr, x, integrateByParts, rationalToo: true, overAQuadraticInTheSine: true)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheHyperbolicTangent(expr, x, integrateByParts)) is { }) return answer;
// A rational function of a sine or a cosine over a quadratic in it with a symbol
// among the coefficients, split over the quadratic's two roots: under the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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 System.Threading;
using System.Threading.Tasks;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A rational function of a hyperbolic sine or cosine over a symbolic quadratic in it, asked under
/// Osborn's rule as one of a sine over a quadratic before the tangent of the half argument: under that
/// tangent Rubi's 6.7.1 <c>sinh(x)^2/(a + b sinh(x) + c sinh(x)^2)</c> is a quartic with symbols in it,
/// and its partial fractions ran past the budget.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AHyperbolicQuotientOverAQuadraticInItAsTheSinesIntegralTest
{
[Theory]
[InlineData("sinh(x)^2/(a + b*sinh(x) + c*sinh(x)^2)")]
[InlineData("sinh(x)^3/(a + b*sinh(x) + c*sinh(x)^2)")]
[InlineData("cosh(x)^2/(a + b*cosh(x) + c*cosh(x)^2)")]
public void IsAnswered(string integrand)
{
Entity? answer = null;
// A thread of its own, so that the guard is not spent waiting behind other tests' work.
var task = Task.Factory.StartNew(() => answer = integrand.ToEntity().Integrate("x"),
CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}");
var integral = answer!;
Assert.DoesNotContain("integral(", integral.Stringize());
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());
foreach (var at in new[] { -1.7, -0.6, 0.4, 1.3, 2.2 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
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