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 @@ -221,6 +221,21 @@ factors written together, and is counted so ([#718](https://github.com/asc-commu
| `"sec(x)^2*sin(x)/(a + b*sin(x))^3".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 5,017 characters |
| `"tan(x)^2/(a + b*sin(x))^3".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 4,816 characters |

### A product of sums in the symbols is multiplied out as a polynomial in the partial fractions

**Answers where there were none.** `1/((a + b x^2)^3 (c + d x^2)^3)` ran past a minute, with twelve more of Rubi's
that put a symbolic quadratic to a power beside other factors: the series at its roots multiplies coefficients
that are products of powers of sums in the symbols, and multiplying those out term by term as expressions was the
whole of the time. They are multiplied out as polynomials now
([#718](https://github.com/asc-community/AngouriMath/issues/718), PR
[#1833](https://github.com/asc-community/AngouriMath/pull/1833)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/((a + b*x^2)^3*(c + d*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 15,289 characters |
| `"1/((d + e*x)^2*(a + b*x + c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 11,274 characters |
| `"1/((a + a*sec(e + f*x))^(5/2)*(c + d*sec(e + f*x))^3)".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 9,155 characters |

### A sine of a double argument beside the argument is written as a product

**Answers where there were none.** `csc(a + b x)^3 sin(2a + 2b x)^7` was declined: the rule that writes multiples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,12 +633,12 @@ internal static bool TrySplitOverWrittenFactors(
internal static Entity InLowestTermsOverTheSymbols(Entity constant)
{
var (above, below) = SingleQuotient.Of(SingleQuotient.Combine(constant).InnerSimplified);
var expandedAbove = Bare(above.Expand().InnerSimplified);
var expandedAbove = Bare(ExpandedOverTheSymbols(above).InnerSimplified);
// Zero as a value, decided at two sets of pinned symbols: `b (a + b (-a/b))` is
// zero and neither the expansion nor the evaluation of numbers says so.
if (expandedAbove == Integer.Zero || expandedAbove.Evaled is Complex { IsZero: true } || IsZeroAtPinnedSymbols(expandedAbove))
return Integer.Zero;
var expandedBelow = Bare(below.Expand().InnerSimplified);
var expandedBelow = Bare(ExpandedOverTheSymbols(below).InnerSimplified);
// A number is folded already, and stays a number.
if ((expandedAbove.Vars.Any() || expandedBelow.Vars.Any())
&& (HoldsTheImaginaryUnit(expandedAbove) || HoldsTheImaginaryUnit(expandedBelow))
Expand All @@ -659,6 +659,34 @@ internal static Entity InLowestTermsOverTheSymbols(Entity constant)
return WithThePrimitiveDenominator(expandedBelow == Integer.One ? expandedAbove : expandedAbove / expandedBelow);
}

/// <summary>
/// <paramref name="expr"/> multiplied out: as a polynomial over the rationals in its
/// symbols where it is one, and by <see cref="Entity.Expand"/> where it is not.
/// </summary>
/// <remarks>
/// <see cref="Entity.Expand"/> multiplies a product of sums out by enumerating the
/// combinations of their terms as expressions. The coefficients the series at a repeated
/// quadratic makes are products of powers of sums in the symbols, and that enumeration was
/// where all of the eighteen seconds of <c>1/((1 + x^2)^2 (c + d + 2d x^2)^3 (1 + 2x^2))</c>
/// went. A polynomial collects its terms as it multiplies.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
private static Entity ExpandedOverTheSymbols(Entity expr)
{
// Only where there is a product to multiply out: a sum or a quotient of sums already
// written term by term keeps the spelling every other step reads it in.
if (!expr.Nodes.Any(node => node is Powf(Sumf or Minusf, Integer { EInteger: var power }) && power.CompareTo(EInteger.One) > 0
|| node is Mulf(var left, var right) && left.Nodes.Any(n => n is Sumf or Minusf) && right.Nodes.Any(n => n is Sumf or Minusf)))
return expr.Expand();
var variables = expr.Vars.OrderBy(v => v.Name, System.StringComparer.Ordinal).ToList();
if (variables.Count == 0 || variables.Count > MultivariatePolynomial.MaxVariables)
return expr.Expand();
var indices = new Dictionary<Variable, int>();
for (var i = 0; i < variables.Count; i++)
indices[variables[i]] = i;
return MultivariatePolynomial.TryParse(expr, indices) is { } polynomial ? polynomial.ToEntity(variables) : expr.Expand();
}

/// <summary>Whether a number off the real line is among the nodes of <paramref name="expr"/>.</summary>
private static bool HoldsTheImaginaryUnit(Entity expr)
=> expr.Nodes.Any(node => node is Complex number && number is not Real && !number.ImaginaryPart.IsZero);
Expand Down Expand Up @@ -1882,6 +1910,7 @@ private static bool TrySolveOverPolynomials(Entity[][] matrix, Entity[] rhs, [No
indices[symbols[i]] = i;
var rows = rhs.Length;
var width = matrix[0].Length;
if (System.Environment.GetEnvironmentVariable("TSOP_DBG") is not null) System.Console.Error.WriteLine($"tsop rows={rows} width={width} symbols={symbols.Count} maxterms={matrix.SelectMany(r => r).Concat(rhs).Max(e => e.Complexity)}");
// The augmented matrix, the right-hand side its last column.
var augmented = new MultivariatePolynomial[rows][];
for (var row = 0; row < rows; row++)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// 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 partial fractions over a symbolic quadratic to a power, beside other quadratics: the
/// series at its roots, with its coefficients multiplied out as polynomials in the symbols.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ARepeatedSymbolicQuadraticIntegralTest
{
[Theory]
[InlineData("1/((1 + x^2)^2*(c + d + 2*d*x^2)^3*(1 + 2*x^2))")]
public void OverItsPowers(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 40000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.4).Substitute("c", 0.9).Substitute("d", 0.6).Substitute("f", 0.7).Substitute("g", 0.2);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -2.1, -0.4, 0.3, 0.6, 1.6, 2.5 })
{
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