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 @@ -614,6 +614,23 @@ carried the discriminant's being real in its condition, and held nowhere when it
| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().DomainCondition` | `false` | `not x = 0` |
| `"1/(sqrt(a + i*a*x)*(c + d*x)^(3/2)*(a - i*a*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer with no value at any point | 3,267 characters, defined |

### A rational function over a binomial of degree three or four beside other factors is split over them

**Answers where there were none.** Rubi's 4.3.7 `1/(a + b tan(c + d x)^3)` and its square, and 6.1.7
`sech(c + d x)/(a + b sqrt(sinh(c + d x)))` and its square, were declined. Under the tangent and under the root of the
sine they are rational functions over `(a + b u^3) (1 + u^2)` and `(a + b u) (1 + u^4)`, and the split over the
factors as written read linear and quadratic ones only. It now reads one binomial of degree three or four, written
once, beside factors of degree one or two, and the rules for a polynomial over a binomial answer each piece; not
beside a power of the variable alone, which those rules read term by term, nor beside a second such binomial
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(a+b*tan(c+d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 1,853 characters |
| `"1/(a+b*tan(c+d*x)^3)^2".ToEntity().Integrate("x")` | `integral(...)` | 3,026 characters |
| `"sech(c+d*x)/(a+b*sqrt(sinh(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 3,325 characters |
| `"sech(c+d*x)/(a+b*sqrt(sinh(c+d*x)))^2".ToEntity().Integrate("x")` | `integral(...)` | 4,614 characters |

### A rational function over the quadratic of an inverse tangent beside it is split

**Answers where there were none.** `x^4 atan(a x)/(c + a^2 c x^2)` ran out of time, and so did three more of Rubi's 5.3.4.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ internal static bool TrySplitOverWrittenFactors(
// coefficients free of x; a factor free of x is a constant and comes out in front.
var factors = new List<(Entity Factor, int Degree)>();
Entity constant = Integer.One;
var binomialsPastTheSecondDegree = 0;
var aPowerOfXAlone = false;
foreach (var part in Mulf.LinearChildren(denominator))
{
if (!part.ContainsNode(x))
Expand All @@ -464,8 +466,16 @@ internal static bool TrySplitOverWrittenFactors(
if (!TreeAnalyzer.TryGetPolynomial(toRead, x, out var read) || read.Count == 0)
return false;
var degree = read.Keys.Max()!;
if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() is not (1 or 2))
// And a binomial of degree three or four written once, `a + b x^3`: the system below
// reads any degree, and the rules for a polynomial over a binomial answer each
// piece -- `1/((a + b u^3) (1 + u^2))`, which the tangent makes of Rubi's
// `1/(a + b tan(c + d x)^3)`, was declined for want of a split.
var aBinomialOnce = read.Count == 2 && multiplicity == 1 && degree.CanFitInInt32() && degree.ToInt32Unchecked() is 3 or 4;
if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() is not (1 or 2) && !aBinomialOnce)
return false;
if (aBinomialOnce)
binomialsPastTheSecondDegree++;
aPowerOfXAlone |= read.Count == 1;
// A repeated quadratic has a rule to land on only with a symbol somewhere,
// the split by residues below; over the rationals the system would hand it a
// cubic over the square, which no rule reads.
Expand All @@ -476,6 +486,15 @@ internal static bool TrySplitOverWrittenFactors(
return false;
factors.Add((part.InnerSimplified, degree.ToInt32Unchecked() * multiplicity));
}
// Not beside a power of x alone, which is the rules' for `x^m` over a binomial, term by
// term: the cube root's substitution makes Rubi's 1.1.3.4 `(a + b x^3)^(1/3)/(x^5 (c + d x^3))`
// a polynomial over `u^3 (c + (a d - b c) u^3)` with `a^9 c^36 d^9` in it, and split over
// the two, it came back in 12,377 characters where those rules write 1,964. Nor beside a
// second such binomial: `x^7 (a + b x^3)^(1/3)/(c + d x^3)` is one over `(b u^3 - 1)` and
// `(c + (a d - b c) u^3)`, whose system with symbols in it came back with `a^17 b^16 c^43`
// and a wrong answer, where the rules for the cube root answer it in 2,861 characters.
if (binomialsPastTheSecondDegree > 1 || binomialsPastTheSecondDegree == 1 && aPowerOfXAlone)
return false;
// Two linear factors with one root are one factor: `(a + b x)(a + x b)^2` is
// written so by the rules that make a factor monic and gather its powers, and
// as two distinct factors the decomposition has no answer -- its coefficients
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// 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 rational function with symbols in it over a product written with a binomial of degree three
/// or four in it, split over the factors as written by undetermined coefficients: each piece is a
/// polynomial over a binomial, or over a linear or a quadratic, which the rules for those answer.
/// The split read only linear and quadratic factors, and `1/((a + b u^3) (1 + u^2))`, which the
/// tangent makes of Rubi's 4.3.7 `1/(a + b tan(c + d x)^3)`, was declined.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ARationalFunctionOverAWrittenBinomialOfDegreeThreeOrFourIntegralTest
{
[Theory]
[InlineData("1/((a+b*x^3)*(1+x^2))")]
[InlineData("1/((a+b*x^4)*(c+x^2))")]
[InlineData("1/(a+b*tan(c+d*x)^3)")]
[InlineData("1/(a+b*tan(c+d*x)^3)^2")]
public void DifferentiatesBack(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.3, -0.6, -0.2, 0.3, 0.8, 1.4 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN || got.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}

/// <summary>
/// Not split beside a power of the variable alone, nor beside a second such binomial: the cube
/// root's substitution makes Rubi's 1.1.3.4 rows rational functions over <c>u^3 (c + k u^3)</c>
/// and over <c>(b u^3 - 1)(c + k u^3)</c>, and split, they came back in 12,377 and 27,590
/// characters with <c>c^36</c> and <c>c^43</c> in them, the second wrong, where the rules for
/// the cube root write 1,964 and 2,861.
/// </summary>
[Theory]
[InlineData("(a+b*x^3)^(1/3)/(x^5*(c+d*x^3))")]
[InlineData("x^7*(a+b*x^3)^(1/3)/(c+d*x^3)")]
public void IsNotSplitBesideAPowerOfXOrASecondBinomial(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 5000, $"the antiderivative of {integrand} is {text.Length} characters");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { 0.3, 0.6, 0.9, 1.2, 1.5, 1.8 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN || got.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading