diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 409cf8bd6..466284748 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -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 diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs index 326848ed9..d02fb4980 100644 --- a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs +++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs @@ -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)) @@ -659,6 +659,34 @@ internal static Entity InLowestTermsOverTheSymbols(Entity constant) return WithThePrimitiveDenominator(expandedBelow == Integer.One ? expandedAbove : expandedAbove / expandedBelow); } + /// + /// multiplied out: as a polynomial over the rationals in its + /// symbols where it is one, and by where it is not. + /// + /// + /// 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 1/((1 + x^2)^2 (c + d + 2d x^2)^3 (1 + 2x^2)) + /// went. A polynomial collects its terms as it multiplies. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + 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(); + for (var i = 0; i < variables.Count; i++) + indices[variables[i]] = i; + return MultivariatePolynomial.TryParse(expr, indices) is { } polynomial ? polynomial.ToEntity(variables) : expr.Expand(); + } + /// Whether a number off the real line is among the nodes of . private static bool HoldsTheImaginaryUnit(Entity expr) => expr.Nodes.Any(node => node is Complex number && number is not Real && !number.ImaginaryPart.IsZero); @@ -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++) diff --git a/Sources/Tests/UnitTests/Calculus/ARepeatedSymbolicQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARepeatedSymbolicQuadraticIntegralTest.cs new file mode 100644 index 000000000..f30fb119a --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ARepeatedSymbolicQuadraticIntegralTest.cs @@ -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 +{ + /// + /// 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. + /// #718 + /// + [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}"); + } + } +}