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 @@ -1197,6 +1197,21 @@ and split over all `n` roots of the binomial as the reciprocal alone already was
| `"sinh(c+d*x)^6/(a+b*sinh(c+d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 1,517 characters |
| `"sin(x)^2/(a+b*sin(x)^5)".ToEntity().Integrate("x")` | `integral(...)` | 2,475 characters |

### A sum of products of powers is read with a base a term lacks there to the power 0

**Answers where there were none.** A sum of products of powers is read as the bases' common product times a bracket,
which the derivative of a product answers. A base missing from a term is now counted there to the power 0, where its
exponents elsewhere are whole: `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is
`(e^x + x)^(-1/2) ((1 + e^x) x + 2 (e^x + x))`, the derivative of `2 x sqrt(e^x + x)`, and with the bases of every term
asked to be the same, `x` and `1 + e^x` in the first term only, nothing read it. Rubi's 1.3.2 and 2.3
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(1 + e^x)*x/sqrt(e^x + x) + 2*sqrt(e^x + x)".ToEntity().Integrate("x")` | `integral(...)` | `2 x sqrt(e^x + x)` |
| `"1/5*x^2*(5*e^x + 3*x^2)/sqrt(5*e^x + x^3) + 4/5*x*sqrt(5*e^x + x^3)".ToEntity().Integrate("x")` | `integral(...)` | `2/5 x^2 sqrt(5 e^x + x^3)` |
| `"(1 - x^6)^(2/3) + (1 - x^6)^(2/3)/x^6".ToEntity().Integrate("x")` | `integral(...)` | `-1/5 (1 - x^6)^(5/3) x^(-5)` |

### A rational function times a logarithm of nested powers of linears is integrated by parts

**Answers where there were none.** `ln(e (f (a + b x)^p (c + d x)^q)^r)` was declined, and so were four more of Rubi's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17541,11 +17541,12 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L)
private const int MaximumTowerDegree = 4;

/// <summary>
/// <paramref name="sum"/>, every term of which is a constant times powers of the same
/// bases with <paramref name="x"/> in them, as the product of those bases to the least of
/// each one's exponents times the sum of what is left. Null where a base is not in every
/// term, where no exponent of one is the least, or where the least are all whole, which
/// the rational rules read.
/// <paramref name="sum"/>, every term of which is a constant times powers of bases with
/// <paramref name="x"/> in them, as the product of those bases to the least of each one's
/// exponents times the sum of what is left, a base missing from a term counted there to the
/// power 0. Null where a base with an exponent that is not whole is not in every term,
/// where no exponent of one is the least, or where the least are all whole, which the
/// rational rules read.
/// </summary>
private static Entity? WithTheCommonPowersOut(Entity sum, Entity.Variable x)
{
Expand Down Expand Up @@ -17581,16 +17582,30 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L)
}
if (terms.Count < 2)
return null;
// The bases in every term; a function in some of them only -- a sine or cosine, an
// exponential or a sum of them -- stays in the bracket, where the rule raises it from
// nothing: `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for
// The bases of every term. A base missing from a term is there to the power 0, where its
// exponents elsewhere are whole: `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is
// `(e^x + x)^(-1/2) ((1 + e^x) x + 2 (e^x + x))`, the derivative of `2 x sqrt(e^x + x)`,
// and with the bases of every term asked to be the same, nothing read it. A function in
// some terms only -- a sine or cosine, an exponential or a sum of them -- is so raised
// from nothing in the bracket, and the least exponents may then all be whole:
// `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for
// `R = x^3 + 3 sin(a + b x)`, is `x/sqrt(R)` times a bracket with the cosine in it.
var bases = terms[0].Powers.Keys.Where(@base => terms.All(term => term.Powers.ContainsKey(@base))).ToList();
var bases = terms.SelectMany(term => term.Powers.Keys).Distinct().ToList();
var functionsInSomeTerms = bases.Any(@base => !terms.All(term => term.Powers.ContainsKey(@base))
&& terms.All(term => !term.Powers.TryGetValue(@base, out var exponent) || exponent == Number.Integer.One)
&& IsAFunctionToRaise(@base, x));
if (bases.Count == 0)
return null;
var functionsInSomeTerms = terms.Any(term => term.Powers.Count != bases.Count);
if (functionsInSomeTerms && terms.Any(term => term.Powers.Any(pair => !bases.Contains(pair.Key) && (pair.Value != Number.Integer.One || !IsAFunctionToRaise(pair.Key, x)))))
return null;
foreach (var @base in bases)
{
if (terms.All(term => term.Powers.ContainsKey(@base)))
continue;
if (terms.Any(term => term.Powers.TryGetValue(@base, out var exponent) && exponent is not Number.Integer))
return null;
foreach (var (_, powers) in terms)
if (!powers.ContainsKey(@base))
powers[@base] = Number.Integer.Zero;
}
// For each base the least of its exponents, against which every other is more by a
// whole number or by a sum of symbols with no sign in front: `x^(m - 1)` beside
// `x^(m + n - 1)` leaves `x^n` in the bracket, as the derivative of `a + b x^n` does.
Expand Down Expand Up @@ -17625,14 +17640,12 @@ static bool More(Entity difference)
foreach (var @base in bases)
if (Difference(powers[@base], least[@base]) is var raised && raised != Number.Integer.Zero)
rest = rest * (raised == Number.Integer.One ? @base : MathS.Pow(@base, raised));
foreach (var function in powers.Keys)
if (!bases.Contains(function))
rest = rest * function;
bracket = bracket == Number.Integer.Zero ? rest : bracket + rest;
}
Entity common = Number.Integer.One;
foreach (var @base in bases)
common = common * MathS.Pow(@base, least[@base]);
if (least[@base] != Number.Integer.Zero)
common = common * MathS.Pow(@base, least[@base]);
return common * bracket;
}

Expand Down Expand Up @@ -17850,7 +17863,8 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R
// bracket, which is the shape read below: `m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1)
// (a + b x^n)^(p - 1)` is `x^(m - 1) (a + b x^n)^(p - 1) (m (a + b x^n) + b n p x^n)`,
// the derivative of `x^m (a + b x^n)^p`. As a sum it was split, and neither term is
// elementary.
// elementary. A base with whole exponents may be missing from some terms: Rubi's
// `(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)` is `(2 x sqrt(e^x + x))'`.
if (expr is Sumf or Minusf)
return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x, integrateByParts) : null;
// A bracket that is a polynomial with a power of x in every term has that power taken out, to
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The derivative of a product of powers written out as a sum whose terms do not all hold the
/// same bases: <c>(1 + e^x) x/sqrt(e^x + x) + 2 sqrt(e^x + x)</c> is <c>(2 x sqrt(e^x + x))'</c>,
/// with <c>x</c> and <c>1 + e^x</c> in the first term only. Neither term is elementary on its
/// own, and the sum was declined. Rubi's 2.3:866-877.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ADerivativeOfAProductOfPowersWrittenAsASumIntegralTest
{
[Theory]
[InlineData("(1 + e^x)*x/sqrt(e^x + x) + 2*sqrt(e^x + x)")]
[InlineData("x/sqrt(e^x + x) + e^x*x/sqrt(e^x + x) + 2*sqrt(e^x + x)")]
[InlineData("1/5*x^2*(5*e^x + 3*x^2)/sqrt(5*e^x + x^3) + 4/5*x*sqrt(5*e^x + x^3)")]
[InlineData("(-1)/(e^x + x)^(1/3) + x/(e^x + x)^(1/3) - (e^x + x)^(2/3)")]
[InlineData("2*x/(e^x + x)^(1/3) + 2*e^x*x/(e^x + x)^(1/3) + 3*(e^x + x)^(2/3)")]
[InlineData("x*(a*e^x + b)/sqrt(a*e^x + b*x) + 2*sqrt(a*e^x + b*x)")]
public void IsTheProductRaised(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 200, $"{text.Length} characters of answer for {integrand}: {text}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -0.4, 0.3, 0.8, 1.3, 2.1, 2.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.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