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 @@ -247,6 +247,24 @@ out of it ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
| `"(a*m*x^m+b*n*q*ln(c*x^n)^(q-1))/(x*(a*x^m+b*ln(c*x^n)^q))".ToEntity().Integrate("x")` | `integral(...)` | `ln(a x^m + b ln(c x^n)^q)` |
| `"(a*(m-1)*x^(m-1)+b*n*q*ln(c*x^n)^(q-1))/(a*x^m+b*x*ln(c*x^n)^q)".ToEntity().Integrate("x")` | `integral(...)` | `ln(a x^(m - 1) + b ln(c x^n)^q)` |

### A power of a long sum is expanded without visiting the combinations it does not return

**Answers where there were none.** Expanding `(t_1 + ... + t_n)^k` enumerates the ways to share `k`
among the `n` terms, and the enumeration visited every prefix of every length, about
`2^(n + k - 1)` of them for the `C(n + k - 1, k)` it returns; squaring a sum of thirty terms ran past
a minute. It now stops a branch where too few values are left to fill it, which changes no term and
no order. Five integrals whose working squares such a sum were past the budget and are answered
([#233](https://github.com/asc-community/AngouriMath/issues/233)); this branch's master ran past
150 s on each.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/((a*g+b*g*x)^2*(A+B*ln(k*((a+b*x)/(c+d*x))^n))^2)".ToEntity().Integrate("x")` | `integral(...)` | 412 characters |
| `"1/((a*g+b*g*x)^2*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | past 60 s | 358 characters |
| `"(a+a*sin(k+f*x))^(3/2)*(A+B*sin(k+f*x))/(c+d*sin(k+f*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 5,373 characters |
| `"sech(x)^2*tanh(x)^2/(2+tanh(x)^3)^2".ToEntity().Integrate("x")` | past 60 s | 117 characters |
| `"coth(x)^3*csch(x)*sqrt(1+csch(x))".ToEntity().Integrate("x")` | `integral(...)` | 325 characters |

### A rational evaluated at a higher precision than before has all of its digits

**More digits where there were a hundred.** A rational kept its decimal value in the precision current when it was
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -734,3 +734,6 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed

[Tests/UnitTests/Calculus/HyperbolicIntegralIntegrationTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Common/SimplifyOverASymbolicCommonDenominatorTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
11 changes: 10 additions & 1 deletion Sources/AngouriMath/Functions/TreeAnalyzer/Expansion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,18 @@ internal static IEnumerable<List<EInteger>> CombinateSums(EInteger itemCount, EI
// Combinations(2, 6, 3) ->
// { 4, 3, 2 }, { 5, 3, 2 }, { 6, 3, 2 }, { 5, 4, 2 }, { 6, 4, 2 },
// { 6, 5, 2 }, { 5, 4, 3 }, { 6, 4, 3 }, { 6, 5, 3 }, { 6, 5, 4 }
//
// The loop stops where too few values are left above i to fill the remaining
// cells, since a branch past that yields nothing. Without the bound every prefix of
// every length was visited -- about 2^(itemCount + targetSum - 1) of them for
// C(itemCount + targetSum - 1, targetSum) results -- so squaring a sum of thirty
// terms, 465 terms and well inside MaxExpansionTermCount, ran past a minute, and two
// more terms took four times as long. The bound changes no result and no order.
// https://github.com/asc-community/AngouriMath/issues/233
static IEnumerable<List<EInteger>> Combinations(EInteger min, EInteger max, EInteger cellCount)
{
for (EInteger i = min; i <= max; i++)
var last = cellCount.Sign > 0 ? max - cellCount + 1 : max;
for (EInteger i = min; i <= last; i++)
if (!cellCount.Equals(EInteger.One))
foreach (var l in Combinations(i + 1, max, cellCount - 1))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Common
{
/// <summary>
/// <c>Simplify</c> returns on quotients over a symbolic denominator. Bringing two of their
/// fractions over a common denominator re-simplifies the cross products, and reading those as
/// polynomials expands squares of sums of thirty terms and more, whose exponent splits were
/// enumerated in time exponential in the number of terms. The integrator met them after a
/// substitution. <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Common")]
public sealed class SimplifyOverASymbolicCommonDenominatorTest
{
[Fact]
public void TheSquareOfALongSumExpands()
{
var names = Enumerable.Range(0, 30).Select(i => "s" + (char)('a' + i / 26) + (char)('a' + i % 26)).ToArray();
var sum = string.Join(" + ", names);
var expanded = Run(() => $"({sum})^2".ToEntity().Expand());
// Thirty squares and 435 products of two, none of which combine.
Assert.Equal(465, Entity.Sumf.LinearChildren(expanded).Count);
var at = expanded;
for (var i = 0; i < names.Length; i++)
at = at.Substitute(names[i], i + 1);
Assert.Equal((Entity)(465 * 465), at.Evaled);
}

// Each returns as it was written: the search finds nothing shorter. Under a second or
// three each on a desktop, where the first did not return in ten minutes and the
// third took forty-seven seconds.
[Theory]
[InlineData("u^4/((c*u + d*(2 - u))^2*(2 - u))")]
[InlineData("u^3/((c*u + d)^2*(2 - u))")]
[InlineData("((A + B*(1 - 2*c/d))*sin(h)*(1 - u^2) + B*(-1/2 + c/d - c^2/(2*d^2))*sin(h))/(c + d*(2*(1 - u^2) - 1))^2/(-sin(h))")]
public void ReturnsWithTheValueItWasGiven(string written)
{
var expression = written.ToEntity();
var simplified = Run(() => expression.Simplify(1));
var compared = 0;
foreach (var shift in new[] { 0.0, 0.37, 1.13 })
{
Entity Pinned(Entity e) => e
.Substitute("A", 1.3 + shift).Substitute("B", -0.7 + shift).Substitute("c", 0.4 + shift)
.Substitute("d", 1.9 - shift).Substitute("h", 0.6 + shift).Substitute("u", 0.3 + shift);
var want = Pinned(expression).EvalNumerical();
var got = Pinned(simplified).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)),
$"{written} simplified to {simplified}, which is {got} where the expression is {want}");
}
Assert.Equal(3, compared);
}

/// <summary>
/// On a thread of its own, so that a test host whose pool is busy cannot hold it back,
/// and with a wall far above the run, so that the bound is reached only by a computation
/// that does not finish.
/// </summary>
private static Entity Run(Func<Entity> work)
{
var task = Task.Factory.StartNew(work, CancellationToken.None,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
Assert.True(task.Wait(TimeSpan.FromMinutes(2)), "did not return in two minutes");
return task.Result;
}
}
}
Loading