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 @@ -396,6 +396,24 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |

### A constant sum that comes to a number is folded before an integral is asked

**Answers where there were none.** `1/sqrt(2 + 2 a - 2 (1 + a) + c x^4)` was declined, and so was Rubi's 1.1.1.2
`(c + d x)^(1 + 2 n - 2 (1 + n))/(a + b x)^2`. The terms of a sum free of the variable that come to a number once
multiplied out are now written as that number before the integral is asked: `2 + 2 a - 2 (1 + a)` is `0` and
`1 + 2 n - 2 (1 + n)` is `-1` ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/sqrt(2+2*a-2*(1+a)+c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 44 characters |
| `"(c+d*x)^(1+2*n-2*(1+n))/(a+b*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 255 characters |

**A changed answer.** An integrand with such a sum as a coefficient is integrated with the number in its place.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(2+2*c-2*(1+c))*x^4".ToEntity().Integrate("x")` | `(2 + 2 * c - 2 * (1 + c)) * x ^ 5 / 5 + C` | `0 + C` |

### Powers of multiples of powers of one linear are gathered into one power of it

**Answers where there were none.** `1/(sqrt(-2 - b x) sqrt(2 + b x))` was declined, and so were four more of Rubi's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28805,6 +28805,43 @@ static bool Through(Entity node, Entity.Variable x)
}
}

/// <summary>
/// The terms of a sum free of the variable that come to a number once multiplied out, written
/// as that number: <c>2 + 2 a - 2 (1 + a)</c> is <c>0</c> and <c>1 + 2 n - 2 (1 + n)</c> is
/// <c>-1</c>, which neither the simplification nor the rules reading a coefficient or an
/// exponent see. Rubi's 1.2.2.2 <c>1/sqrt(a + (2 + 2 c - 2 (1 + c)) x^4)</c> and 1.1.1.2
/// <c>(c + d x)^(1 + 2 n - 2 (1 + n))/(a + b x)^2</c> were declined where
/// <c>1/sqrt(a)</c> and <c>1/((c + d x) (a + b x)^2)</c> are answered.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </summary>
internal static Entity? SolveByFoldingConstantSumsToTheirNumbers(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
// Only where a product holds a sum, which multiplying out is for.
static bool Distributes(Entity term) => term.Nodes.Any(node => node is Mulf && node.DirectChildren.Any(child => child is Sumf or Minusf));
if (!expr.Nodes.Any(node => node is Sumf or Minusf && !node.ContainsNode(x) && Distributes(node)))
return null;
var folded = expr.Replace(node =>
{
if (node is not (Sumf or Minusf))
return node;
var terms = Sumf.LinearChildren(node).ToList();
var free = terms.Where(term => !term.ContainsNode(x)).ToList();
if (free.Count < 2 || !free.Any(Distributes))
return node;
if (free.Aggregate((sum, term) => sum + term).Expand().InnerSimplified is not Number number)
return node;
return terms.Where(term => term.ContainsNode(x)).Aggregate((Entity)number, (sum, term) => sum + term);
});
if (folded == expr)
return null;
var simplified = folded.InnerSimplified;
if (simplified == expr || simplified == expr.InnerSimplified)
return null;
return Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts);
}

/// <summary>
/// A power of a constant multiple of a radicand that a logarithm holds under a root,
/// written over that radicand: <c>acosh(c x)</c> is <c>ln(c x + sqrt(c^2 x^2 - 1))</c>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// dropped it re-enabled integration by parts one level below the call that
// switched it off -- which is a cycle, since by parts calls back into here.
// `x * ln(x)` went round it until the stack ran out.
// A sum free of the variable that comes to a number multiplied out, as that number,
// before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer;
// A product of powers of quotients of linears with one linear in two of them, written
// over each linear once with the constant that takes in front. First, since it answers
// the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// 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 terms of a sum free of the variable that come to a number multiplied out, folded to it
/// before the integral is asked: Rubi's 1.2.2.2 and 1.1.1.2 spell a zero coefficient
/// <c>2 + 2 a - 2 (1 + a)</c> and an exponent of <c>-1</c> as <c>1 + 2 n - 2 (1 + n)</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ConstantSumsFoldedToTheirNumbersIntegralTest
{
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

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/sqrt(a+(2+2*c-2*(1+c))*x^4)")]
[InlineData("1/sqrt(2+2*a-2*(1+a)+c*x^4)")]
[InlineData("1/sqrt(2-2*(1+a)+c*x^4+2*a)")]
[InlineData("(c+d*x)^(1+2*n-2*(1+n))/(a+b*x)^2")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25), ("c", 1.5), ("d", -0.6), ("n", 1.0 / 3));
DifferentiatesBack(integrand, ("a", -0.75), ("b", -0.7), ("c", 2.5), ("d", 0.4), ("n", 2.3));
}

[Fact]
public void LeavesASumThatIsNotANumber()
{
// `m + 2 (1 + n)` is no number multiplied out, and the power is integrated as written.
var integral = "x^(m+2*(1+n))".ToEntity().Integrate("x").Stringize();
Assert.Contains("1 + n", integral);
}
}
}
Loading