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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,24 @@ split too, and the powers of `P` gather ([#718](https://github.com/asc-community
| `"(d + k*x)^m*(f + g*x)^3/(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2)^m".ToEntity().Integrate("x")` | `integral(...)` | 1,817 characters |
| `"(1 - x^4)^n/(1 + x + x^2 + x^3)^n".ToEntity().Integrate("x")` | `integral(...)` | 147 characters |

### A power of a product beside a whole power of one of its factors below the bar is split

**Answers where there were none.** `1/((d + k x)^4 (a d k + (c d^2 + a k^2) x + c d k x^2)^(3/2))` was declined
past any budget. The split of a root of a product beside a root of a factor now also takes a factor to a whole
power of two or more below the bar, and a product to a whole power beside a root of a factor, so the powers of
the factor gather. It is asked before the routes that read the product whole: `(d + k x)^(7/2)/(a d k + (c d^2
+ a k^2) x + c d k x^2)` was answered by them in 29,285 characters
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/((d + k*x)^4*(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 663 characters |
| `"1/(sqrt(d + k*x)*(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,435 characters |
| `"(d + k*x)^(7/2)/(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 1,611 characters |
| `"(A + B*x)*(a*c + b*c*x)^m/(a^2 + 2*a*b*x + b^2*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 223 characters |
| `"(f + g*x)/((d + k*x)^3*(c*d^2 - b*d*k - b*k^2*x - c*k^2*x^2)^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | 1,156 characters |
| `"(12 - 3*k^2*x^2)^(1/4)/(2 + k*x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 613 characters |

### A secant or a cosecant beside a power of a sum of them is read as a power of the cosine or the sine

**Answers where there were none.** `sec(x) sqrt(sec(x) + tan(x))` was declined, with the rest of Rubi's 4.7.7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19360,19 +19360,33 @@ Entity TakeOut(Entity node)
{
if (!Integration.AnsweringTheQuestionAsked || splittingARootOfAProduct)
return null;
var roots = new List<(Entity Node, Entity Base, Entity Exponent)>();
Gather(expr);
var roots = new List<(Entity Node, Entity Base, Entity Exponent, bool Below)>();
Gather(expr, false);
if (roots.Count < 2)
return null;
foreach (var product in roots)
foreach (var factor in roots)
{
// One of the two a root or a symbolic power at least: two whole powers are a
// rational function, which the partial fractions take apart already. And a
// factor to a whole power of two or more: beside a bare one the root was split
// where it read better whole -- Welz's `(1 + x)/((x - 2) sqrt(1 + x^3))` is one
// logarithm, and over `sqrt(1 + x) sqrt(1 - x + x^2)` it was three.
if (ReferenceEquals(product.Node, factor.Node) || product.Base == factor.Base
|| product.Exponent is Number.Integer && factor.Exponent is Number.Integer
|| factor.Exponent is Number.Integer { EInteger: var once } && once.Abs().CompareTo(EInteger.FromInt32(2)) < 0
// A whole power beside a root only where the split gathers powers below
// the bar, and of a sum: above it, `(a + b x)^3 sqrt(c x^2 - a^2 c/b^2)` went
// from 3,540 characters to 33,316, and `x^(5/2)/(b x^2 + c x^4)^3`, a power
// of x, from 1.7 seconds to 7.9.
|| factor.Exponent is Number.Integer && !factor.Below
|| product.Exponent is Number.Integer && factor.Base is not (Sumf or Minusf)
|| ExactlyDivided(product.Base, factor.Base, x) is not { } cofactor || !cofactor.ContainsNode(x))
continue;
var multiple = Variable.CreateUnique(expr, "k_split");
var written = MathS.Pow(factor.Base, product.Exponent) * MathS.Pow(cofactor, product.Exponent);
var constant = product.Node / written;
// A whole power of a product is the product of the powers, with no constant.
Entity constant = product.Exponent is Number.Integer ? Number.Integer.One : product.Node / written;
var rewritten = expr.Replace(node => ReferenceEquals(node, product.Node) ? multiple * written : node).InnerSimplified;
Entity? answer;
splittingARootOfAProduct = true;
Expand All @@ -19391,25 +19405,35 @@ Entity TakeOut(Entity node)
}
return null;

void Gather(Entity node)
void Gather(Entity node, bool below)
{
switch (node)
{
case Mulf(var left, var right):
Gather(left);
Gather(right);
Gather(left, below);
Gather(right, below);
break;
case Divf(var above, var below):
Gather(above);
Gather(below);
case Divf(var above, var under):
Gather(above, below);
Gather(under, !below);
break;
// A symbolic exponent as well: K is exp(2 pi i n r) for a whole n constant
// between the zeros of P and Q, whatever r is. Rubi's 1.2.1.2:2389,
// `(d + e x)^(-3 - 2 p) ((d + e x) (a e + c d x))^p`, gathers into
// `(d + e x)^(-3 - p) (a e + c d x)^p`.
case Powf(var @base, var exponent) when exponent is not Number.Integer && !exponent.ContainsNode(x) && @base.ContainsNode(x)
&& (exponent is Number.Rational || exponent.Vars.Any()):
roots.Add((node, @base, exponent));
roots.Add((node, @base, exponent, below));
break;
// And a whole power, or a sum standing as a factor, beside a root: Rubi's
// 1.2.1.2:2218, `1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))`, is
// `(d + e x)^(-11/2) (a e + c d x)^(-3/2)` up to the constant, and :2280 has the
// product to the first power below the bar beside `sqrt(d + e x)`.
case Powf(var @base, Number.Integer { IsZero: false } whole) when @base is Sumf or Minusf && @base.ContainsNode(x):
roots.Add((node, @base, whole, below ^ whole.EInteger.Sign < 0));
break;
case Sumf or Minusf when node.ContainsNode(x):
roots.Add((node, node, Number.Integer.One, below));
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,12 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and
// asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere.
if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer;
// A root or a power of a product beside a power of one of its factors, the product
// split: `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`.
// Early, since what reads the product whole is slower and longer: Rubi's 1.2.1.2:2218,
// `1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))`, took 27 seconds to
// reach it and its split form takes a quarter of one.
if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer;
// A symbolic trinomial in x^2 below the bar beside a root of a quadratic, over its roots
// in x^2, before anything reads the trinomial whole: asked of it, the routes below ran
// past a minute on Rubi's 1.2.2.4 `sqrt(d + e x^2)/(x^2 (a + b x^2 + c x^4))`.
Expand Down Expand Up @@ -1239,9 +1245,6 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// And a power of a product with a factor under a whole power, that power taken out in
// front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer;
// And a root of a product beside a root of one of its factors, the product split:
// `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`.
if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer;
// Product-to-sum among the rewrites rather than before them, because a product of
// trigonometric functions of *equal* arguments is a power and wants a different tool;
// this only fires where the arguments differ, which is exactly what every substitution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// 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 root of a product beside a whole power of one of its factors, or the product to a whole
/// power beside a root of a factor: split, the powers of the factor gather. Rubi's 1.2.1.2
/// <c>1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))</c>, written here with
/// <c>k</c> for <c>e</c>, which is Euler's number.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfAProductBesideAWholePowerOfAFactorIntegralTest
{
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

// Pinned before it is differentiated: the answer carries the constant the split takes
// out, whose derivative is read where its arguments are numbers.
var pinned = integral.Substitute("C", 0);
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
pinned = pinned.Substitute(name, value);
original = original.Substitute(name, value);
}
var derivative = pinned.Differentiate("x");
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

[Theory]
[InlineData("1/((d+k*x)^4*(a*d*k+(c*d^2+a*k^2)*x+c*d*k*x^2)^(3/2))")]
[InlineData("1/((d+k*x)^(1/2)*(a*d*k+(c*d^2+a*k^2)*x+c*d*k*x^2))")]
[InlineData("(d+k*x)^(7/2)/(a*d*k+(c*d^2+a*k^2)*x+c*d*k*x^2)")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 2), ("c", 1.5), ("d", 1.25), ("k", 2));
DifferentiatesBack(integrand, ("a", -1), ("c", 2), ("d", -0.5), ("k", 1.5));
}
}
}
Loading