From 5f514dceaf78dcf1ba06dd25f3ad0e334d5e8efe Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 31 Oct 2026 18:42:14 +0000 Subject: [PATCH] A Taylor expansion simplifies each derivative before taking the next SingleTaylorExpansionTerms took each derivative from the one before it as it stood, and only the term built from it was simplified. A quotient's derivatives grew about eightfold a step: the ninth of sin(x) / x had 7,797,110 nodes, and 87 simplified, so 7 terms around 1 printed in 36,421 characters and 11 ran out of memory. Each derivative is now simplified before the next is taken, and each coefficient once it is evaluated at the point, so 11 terms are 529 characters in 0.9 s. The partial derivatives of the multivariable expansion are simplified the same way. The sum stops asking for terms once it has them, where it took one derivative more and dropped it. No terms are 0, where the empty sum threw AngouriBugException. A function with no value at the point has no derivatives there either, so its expansion is NaN once, where it was a NaN for each term after as many derivatives. The documented examples of Maclaurin, Taylor and TaylorTerms print the simplified coefficients, cos(1) * (-1) is -cos(1), so they now say what they print and leave DocumentationExamplesRunTest's failing list. The cotan(1 - t) pins in SeriesTest take the simplified coefficients, csc(1)^2 and 2 cot(1) csc(1)^2. e^x / (1 + x^2) still grows about threefold a step: the quotient rule squares the denominator and Simplify does not cancel the factor the numerator's terms share with it. That is Simplify's, #2301. Seven of the new and changed tests fail on master. The suite, the trio, F#, AOT and the eight harnesses pass. Fixes #2291. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 17 +++++ Sources/AngouriMath/Convenience/MathS.cs | 64 +++++++++---------- Sources/AngouriMath/Functions/Series.cs | 56 +++++++++++----- .../Tests/UnitTests/Calculus/SeriesTest.cs | 57 ++++++++++++++++- .../DocumentationExamplesRunTest.cs | 4 -- 5 files changed, 144 insertions(+), 54 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 7a602ae02..ded9f0f7b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -870,6 +870,23 @@ reals, to `1` and `0` ([#2195](https://github.com/asc-community/AngouriMath/issu | `"arg(x)".ToEntity().Compile("x").Call(-2)` | throws `UncompilableNodeException`, `arg` being a variable | `pi` | | `"conj(x)".ToEntity().Compile("x")(3 + 4i)` | throws `UncompilableNodeException`, `conj` being a variable | `3 - 4i` | +### A Taylor expansion simplifies each derivative before taking the next + +**Silent: `MathS.Series.Taylor`, `Maclaurin` and `TaylorTerms` print simplified coefficients, an expansion where the +function has no value is `NaN`, and no terms are `0`.** Each derivative was taken from the one before it as it stood, +and only the term built from it was simplified. A quotient's derivatives grew about eightfold a step: the ninth of +`sin(x) / x` had 7,797,110 nodes, and 87 simplified. One derivative more than the terms kept was also taken, then +dropped. An expansion around a point where the function has no value summed a `NaN` for each term +([#2291](https://github.com/asc-community/AngouriMath/issues/2291)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `MathS.Series.Taylor("sin(x) / x", 11, ("x", 1))` | `OutOfMemoryException` after 32 s with a 2 GB heap | 529 characters, 0.9 s | +| `MathS.Series.Taylor("sin(x) / x", 7, ("x", 1))` | 36,421 characters | `sin(1) + (cos(1) - sin(1)) * (x - 1) + ((-2) * cos(1) + sin(1)) * (x - 1) ^ 2 / 2! + ...` | +| `MathS.Series.Taylor("sin(x) / x", 4, ("x", 0))` | `NaN + NaN + NaN / 2! + NaN / 3!` | `NaN` | +| `MathS.Series.Taylor("sin(x)", 0, ("x", 1))` | throws `AngouriBugException` | `0` | +| `MathS.Series.Taylor("sin(x) + cos(y)", 6, ("x", 1))` | `sin(1) + cos(y) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + cos(1) * (-1) * (x - 1) ^ 3 / 3! + ...` | `cos(y) + sin(1) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + -cos(1) * (x - 1) ^ 3 / 3! + ...` | + ### A logarithm to base 0 has no value **Silent: `log(0, x)` is `NaN`, where it was `0` for a positive `x`.** Evaluation and both compilers took it as diff --git a/Sources/AngouriMath/Convenience/MathS.cs b/Sources/AngouriMath/Convenience/MathS.cs index 2cfee6d72..9ea198721 100644 --- a/Sources/AngouriMath/Convenience/MathS.cs +++ b/Sources/AngouriMath/Convenience/MathS.cs @@ -2681,23 +2681,23 @@ public static class Series /// x ^ 9 / 362880 - x ^ 7 / 5040 + x ^ 5 / 120 - x ^ 3 / 6 + x /// ---------------------- /// sin(x) + cos(y) - /// 1 + (6 * x ^ 5 - 120 * x ^ 3) / 720 + x + (2 * y ^ 4 - 24 * y ^ 2) / 48 + /// x ^ 5 / 120 - x ^ 3 / 6 + x + 1 + y ^ 4 / 24 - y ^ 2 / 2 /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(y) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + cos(1) * (-1) * (x - 1) ^ 3 / 3! + -sin(1) * (-1) * (x - 1) ^ 4 / 4! + cos(1) * (-1) * (-1) * (x - 1) ^ 5 / 5! + /// cos(y) + sin(1) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + -cos(1) * (x - 1) ^ 3 / 3! + sin(1) * (x - 1) ^ 4 / 4! + cos(1) * (x - 1) ^ 5 / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + cos(5) * (-1) * (y - 5) ^ 2) / 2! + (cos(1) * (-1) * (x - 1) ^ 3 + -sin(5) * (-1) * (y - 5) ^ 3) / 3! + (-sin(1) * (-1) * (x - 1) ^ 4 + cos(5) * (-1) * (-1) * (y - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (x - 1) ^ 5 + -sin(5) * (-1) * (-1) * (y - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + -cos(5) * (y - 5) ^ 2) / 2! + (-cos(1) * (x - 1) ^ 3 + sin(5) * (y - 5) ^ 3) / 3! + (sin(1) * (x - 1) ^ 4 + cos(5) * (y - 5) ^ 4) / 4! + (cos(1) * (x - 1) ^ 5 + -sin(5) * (y - 5) ^ 5) / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + cos(5) * (-1) * (z_2 - 5) ^ 2) / 2! + (cos(1) * (-1) * (z_1 - 1) ^ 3 + -sin(5) * (-1) * (z_2 - 5) ^ 3) / 3! + (-sin(1) * (-1) * (z_1 - 1) ^ 4 + cos(5) * (-1) * (-1) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (z_1 - 1) ^ 5 + -sin(5) * (-1) * (-1) * (z_2 - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + -cos(5) * (z_2 - 5) ^ 2) / 2! + (-cos(1) * (z_1 - 1) ^ 3 + sin(5) * (z_2 - 5) ^ 3) / 3! + (sin(1) * (z_1 - 1) ^ 4 + cos(5) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (z_1 - 1) ^ 5 + -sin(5) * (z_2 - 5) ^ 5) / 5! /// ---------------------- /// Received cos(1) /// Received x + -sin(1) * (y - 1) - /// Received cos(1) * (-1) * (y - 1) ^ 2 / 2! - /// Received (-x ^ 3 + -sin(1) * (-1) * (y - 1) ^ 3) / 3! - /// Received cos(1) * (-1) * (-1) * (y - 1) ^ 4 / 4! - /// Received (x ^ 5 + -sin(1) * (-1) * (-1) * (y - 1) ^ 5) / 5! + /// Received -cos(1) * (y - 1) ^ 2 / 2! + /// Received (-x ^ 3 + sin(1) * (y - 1) ^ 3) / 3! + /// Received cos(1) * (y - 1) ^ 4 / 4! + /// Received (x ^ 5 + -sin(1) * (y - 1) ^ 5) / 5! /// /// public static Entity Maclaurin(Entity expr, int degree, params Variable[] exprVariables) @@ -2769,23 +2769,23 @@ public static Entity Maclaurin(Entity expr, int degree, params Variable[] exprVa /// x ^ 9 / 362880 - x ^ 7 / 5040 + x ^ 5 / 120 - x ^ 3 / 6 + x /// ---------------------- /// sin(x) + cos(y) - /// 1 + (6 * x ^ 5 - 120 * x ^ 3) / 720 + x + (2 * y ^ 4 - 24 * y ^ 2) / 48 + /// x ^ 5 / 120 - x ^ 3 / 6 + x + 1 + y ^ 4 / 24 - y ^ 2 / 2 /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(y) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + cos(1) * (-1) * (x - 1) ^ 3 / 3! + -sin(1) * (-1) * (x - 1) ^ 4 / 4! + cos(1) * (-1) * (-1) * (x - 1) ^ 5 / 5! + /// cos(y) + sin(1) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + -cos(1) * (x - 1) ^ 3 / 3! + sin(1) * (x - 1) ^ 4 / 4! + cos(1) * (x - 1) ^ 5 / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + cos(5) * (-1) * (y - 5) ^ 2) / 2! + (cos(1) * (-1) * (x - 1) ^ 3 + -sin(5) * (-1) * (y - 5) ^ 3) / 3! + (-sin(1) * (-1) * (x - 1) ^ 4 + cos(5) * (-1) * (-1) * (y - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (x - 1) ^ 5 + -sin(5) * (-1) * (-1) * (y - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + -cos(5) * (y - 5) ^ 2) / 2! + (-cos(1) * (x - 1) ^ 3 + sin(5) * (y - 5) ^ 3) / 3! + (sin(1) * (x - 1) ^ 4 + cos(5) * (y - 5) ^ 4) / 4! + (cos(1) * (x - 1) ^ 5 + -sin(5) * (y - 5) ^ 5) / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + cos(5) * (-1) * (z_2 - 5) ^ 2) / 2! + (cos(1) * (-1) * (z_1 - 1) ^ 3 + -sin(5) * (-1) * (z_2 - 5) ^ 3) / 3! + (-sin(1) * (-1) * (z_1 - 1) ^ 4 + cos(5) * (-1) * (-1) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (z_1 - 1) ^ 5 + -sin(5) * (-1) * (-1) * (z_2 - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + -cos(5) * (z_2 - 5) ^ 2) / 2! + (-cos(1) * (z_1 - 1) ^ 3 + sin(5) * (z_2 - 5) ^ 3) / 3! + (sin(1) * (z_1 - 1) ^ 4 + cos(5) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (z_1 - 1) ^ 5 + -sin(5) * (z_2 - 5) ^ 5) / 5! /// ---------------------- /// Received cos(1) /// Received x + -sin(1) * (y - 1) - /// Received cos(1) * (-1) * (y - 1) ^ 2 / 2! - /// Received (-x ^ 3 + -sin(1) * (-1) * (y - 1) ^ 3) / 3! - /// Received cos(1) * (-1) * (-1) * (y - 1) ^ 4 / 4! - /// Received (x ^ 5 + -sin(1) * (-1) * (-1) * (y - 1) ^ 5) / 5! + /// Received -cos(1) * (y - 1) ^ 2 / 2! + /// Received (-x ^ 3 + sin(1) * (y - 1) ^ 3) / 3! + /// Received cos(1) * (y - 1) ^ 4 / 4! + /// Received (x ^ 5 + -sin(1) * (y - 1) ^ 5) / 5! /// /// public static Entity Taylor(Entity expr, int degree, params (Variable exprVariable, Entity point)[] exprVariables) @@ -2858,23 +2858,23 @@ public static Entity Taylor(Entity expr, int degree, params (Variable exprVariab /// x ^ 9 / 362880 - x ^ 7 / 5040 + x ^ 5 / 120 - x ^ 3 / 6 + x /// ---------------------- /// sin(x) + cos(y) - /// 1 + (6 * x ^ 5 - 120 * x ^ 3) / 720 + x + (2 * y ^ 4 - 24 * y ^ 2) / 48 + /// x ^ 5 / 120 - x ^ 3 / 6 + x + 1 + y ^ 4 / 24 - y ^ 2 / 2 /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(y) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + cos(1) * (-1) * (x - 1) ^ 3 / 3! + -sin(1) * (-1) * (x - 1) ^ 4 / 4! + cos(1) * (-1) * (-1) * (x - 1) ^ 5 / 5! + /// cos(y) + sin(1) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + -cos(1) * (x - 1) ^ 3 / 3! + sin(1) * (x - 1) ^ 4 / 4! + cos(1) * (x - 1) ^ 5 / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + cos(5) * (-1) * (y - 5) ^ 2) / 2! + (cos(1) * (-1) * (x - 1) ^ 3 + -sin(5) * (-1) * (y - 5) ^ 3) / 3! + (-sin(1) * (-1) * (x - 1) ^ 4 + cos(5) * (-1) * (-1) * (y - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (x - 1) ^ 5 + -sin(5) * (-1) * (-1) * (y - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + -cos(5) * (y - 5) ^ 2) / 2! + (-cos(1) * (x - 1) ^ 3 + sin(5) * (y - 5) ^ 3) / 3! + (sin(1) * (x - 1) ^ 4 + cos(5) * (y - 5) ^ 4) / 4! + (cos(1) * (x - 1) ^ 5 + -sin(5) * (y - 5) ^ 5) / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + cos(5) * (-1) * (z_2 - 5) ^ 2) / 2! + (cos(1) * (-1) * (z_1 - 1) ^ 3 + -sin(5) * (-1) * (z_2 - 5) ^ 3) / 3! + (-sin(1) * (-1) * (z_1 - 1) ^ 4 + cos(5) * (-1) * (-1) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (z_1 - 1) ^ 5 + -sin(5) * (-1) * (-1) * (z_2 - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + -cos(5) * (z_2 - 5) ^ 2) / 2! + (-cos(1) * (z_1 - 1) ^ 3 + sin(5) * (z_2 - 5) ^ 3) / 3! + (sin(1) * (z_1 - 1) ^ 4 + cos(5) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (z_1 - 1) ^ 5 + -sin(5) * (z_2 - 5) ^ 5) / 5! /// ---------------------- /// Received cos(1) /// Received x + -sin(1) * (y - 1) - /// Received cos(1) * (-1) * (y - 1) ^ 2 / 2! - /// Received (-x ^ 3 + -sin(1) * (-1) * (y - 1) ^ 3) / 3! - /// Received cos(1) * (-1) * (-1) * (y - 1) ^ 4 / 4! - /// Received (x ^ 5 + -sin(1) * (-1) * (-1) * (y - 1) ^ 5) / 5! + /// Received -cos(1) * (y - 1) ^ 2 / 2! + /// Received (-x ^ 3 + sin(1) * (y - 1) ^ 3) / 3! + /// Received cos(1) * (y - 1) ^ 4 / 4! + /// Received (x ^ 5 + -sin(1) * (y - 1) ^ 5) / 5! /// /// public static Entity Taylor(Entity expr, int degree, params (Variable exprVariable, Variable polyVariable, Entity point)[] exprToPolyVars) @@ -2948,23 +2948,23 @@ public static Entity Taylor(Entity expr, int degree, params (Variable exprVariab /// x ^ 9 / 362880 - x ^ 7 / 5040 + x ^ 5 / 120 - x ^ 3 / 6 + x /// ---------------------- /// sin(x) + cos(y) - /// 1 + (6 * x ^ 5 - 120 * x ^ 3) / 720 + x + (2 * y ^ 4 - 24 * y ^ 2) / 48 + /// x ^ 5 / 120 - x ^ 3 / 6 + x + 1 + y ^ 4 / 24 - y ^ 2 / 2 /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(y) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + cos(1) * (-1) * (x - 1) ^ 3 / 3! + -sin(1) * (-1) * (x - 1) ^ 4 / 4! + cos(1) * (-1) * (-1) * (x - 1) ^ 5 / 5! + /// cos(y) + sin(1) + cos(1) * (x - 1) + -sin(1) * (x - 1) ^ 2 / 2! + -cos(1) * (x - 1) ^ 3 / 3! + sin(1) * (x - 1) ^ 4 / 4! + cos(1) * (x - 1) ^ 5 / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + cos(5) * (-1) * (y - 5) ^ 2) / 2! + (cos(1) * (-1) * (x - 1) ^ 3 + -sin(5) * (-1) * (y - 5) ^ 3) / 3! + (-sin(1) * (-1) * (x - 1) ^ 4 + cos(5) * (-1) * (-1) * (y - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (x - 1) ^ 5 + -sin(5) * (-1) * (-1) * (y - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (x - 1) + -sin(5) * (y - 5) + (-sin(1) * (x - 1) ^ 2 + -cos(5) * (y - 5) ^ 2) / 2! + (-cos(1) * (x - 1) ^ 3 + sin(5) * (y - 5) ^ 3) / 3! + (sin(1) * (x - 1) ^ 4 + cos(5) * (y - 5) ^ 4) / 4! + (cos(1) * (x - 1) ^ 5 + -sin(5) * (y - 5) ^ 5) / 5! /// ---------------------- /// sin(x) + cos(y) - /// sin(1) + cos(5) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + cos(5) * (-1) * (z_2 - 5) ^ 2) / 2! + (cos(1) * (-1) * (z_1 - 1) ^ 3 + -sin(5) * (-1) * (z_2 - 5) ^ 3) / 3! + (-sin(1) * (-1) * (z_1 - 1) ^ 4 + cos(5) * (-1) * (-1) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (-1) * (-1) * (z_1 - 1) ^ 5 + -sin(5) * (-1) * (-1) * (z_2 - 5) ^ 5) / 5! + /// cos(5) + sin(1) + cos(1) * (z_1 - 1) + -sin(5) * (z_2 - 5) + (-sin(1) * (z_1 - 1) ^ 2 + -cos(5) * (z_2 - 5) ^ 2) / 2! + (-cos(1) * (z_1 - 1) ^ 3 + sin(5) * (z_2 - 5) ^ 3) / 3! + (sin(1) * (z_1 - 1) ^ 4 + cos(5) * (z_2 - 5) ^ 4) / 4! + (cos(1) * (z_1 - 1) ^ 5 + -sin(5) * (z_2 - 5) ^ 5) / 5! /// ---------------------- /// Received cos(1) /// Received x + -sin(1) * (y - 1) - /// Received cos(1) * (-1) * (y - 1) ^ 2 / 2! - /// Received (-x ^ 3 + -sin(1) * (-1) * (y - 1) ^ 3) / 3! - /// Received cos(1) * (-1) * (-1) * (y - 1) ^ 4 / 4! - /// Received (x ^ 5 + -sin(1) * (-1) * (-1) * (y - 1) ^ 5) / 5! + /// Received -cos(1) * (y - 1) ^ 2 / 2! + /// Received (-x ^ 3 + sin(1) * (y - 1) ^ 3) / 3! + /// Received cos(1) * (y - 1) ^ 4 / 4! + /// Received (x ^ 5 + -sin(1) * (y - 1) ^ 5) / 5! /// /// public static IEnumerable TaylorTerms(Entity expr, params (Variable exprVariable, Variable polyVariable, Entity point)[] exprToPolyVars) diff --git a/Sources/AngouriMath/Functions/Series.cs b/Sources/AngouriMath/Functions/Series.cs index 5a192f51e..20474a096 100644 --- a/Sources/AngouriMath/Functions/Series.cs +++ b/Sources/AngouriMath/Functions/Series.cs @@ -24,24 +24,53 @@ internal static Entity SingleTaylorExpansion(Entity expr, int termCount, Variabl { if (termCount < 0) throw new InvalidNumberException($"{nameof(termCount)} is supposed to be at least 0, and {termCount} was given"); + return Sum(SingleTaylorExpansionTerms(expr, exprVariable, polyVariable, point), termCount); + } + + /// + /// The first terms added up, 0 where there are none, and + /// where a term has no value. + /// + /// + /// The term after the last is never asked for, since it costs a derivative more. The + /// expansion of sin(x) / x around 0 has no value, as the function has none there, + /// and neither has any derivative of it; it was the sum of the terms, NaN + NaN + .... + /// https://github.com/asc-community/AngouriMath/issues/2291 + /// + private static Entity Sum(IEnumerable series, int termCount) + { var terms = new List(); - foreach (var term in SingleTaylorExpansionTerms(expr, exprVariable, polyVariable, point)) + using var enumerator = series.GetEnumerator(); + while (terms.Count < termCount && enumerator.MoveNext()) { - if (terms.Count >= termCount) - return TreeAnalyzer.MultiHangBinary(terms, (a, b) => a + b); - terms.Add(term); + if (enumerator.Current.IsNaN) + return Number.Real.NaN; + terms.Add(enumerator.Current); } - throw new AngouriBugException($"We cannot get here, as {nameof(SingleTaylorExpansionTerms)} is supposed to be endless"); + return terms.Count == 0 ? Number.Integer.Zero : TreeAnalyzer.MultiHangBinary(terms, (a, b) => a + b); } + /// + /// Each derivative is simplified before the next is taken from it, and each coefficient + /// once it is evaluated at the point. A quotient's derivative taken as it stands grows about + /// eightfold a step, so the ninth of sin(x) / x had 7,797,110 nodes, and 87 + /// simplified, and the eleventh ran out of memory. Where a coefficient has no value, neither + /// has any after it, so the derivatives stop there. + /// https://github.com/asc-community/AngouriMath/issues/2291 + /// internal static IEnumerable SingleTaylorExpansionTerms(Entity expr, Variable exprVariable, Variable polyVariable, Entity point) { var currExpr = expr; var i = 0; while (true) { - var coef = currExpr.Substitute(exprVariable, point); + var coef = currExpr.Substitute(exprVariable, point).Simplify(); var num = (i == 0 ? coef : coef * (polyVariable - point).Pow(i)).InnerSimplified; + if (num.IsNaN) + { + while (true) + yield return Number.Real.NaN; + } if (num != Number.Integer.Zero) if (i > 1) yield return num / ((Entity)i).Factorial(); @@ -49,7 +78,7 @@ internal static IEnumerable SingleTaylorExpansionTerms(Entity expr, Vari yield return num; else yield return Number.Integer.Zero; - currExpr = currExpr.Differentiate(exprVariable); + currExpr = currExpr.Differentiate(exprVariable).Simplify(); i++; } } @@ -58,14 +87,7 @@ internal static Entity MultivariableTaylorExpansion(Entity expr, int termCount, { if (termCount < 0) throw new InvalidNumberException($"{nameof(termCount)} is supposed to be at least 0, and {termCount} was given"); - var terms = new List(); - foreach (var term in MultivariableTaylorExpansionTerms(expr, exprToPolyVars)) - { - if (terms.Count >= termCount) - return TreeAnalyzer.MultiHangBinary(terms, (a, b) => a + b); - terms.Add(term); - } - throw new AngouriBugException($"We cannot get here, as {nameof(MultivariableTaylorExpansionTerms)} is supposed to be endless"); + return Sum(MultivariableTaylorExpansionTerms(expr, exprToPolyVars), termCount); } internal static IEnumerable MultivariableTaylorExpansionTerms(Entity expr, params (Variable exprVariable, Variable polyVariable, Entity point)[] exprToPolyVars) @@ -119,7 +141,8 @@ internal static IEnumerable MultivariableTaylorExpansionTerms(Entity exp // If no match is found, we add it to the new term list. if (!foundRepeatFlag) { - var newPartialDerivative = partialDerivative.Differentiate(exprVariable); + // Simplified before the next is taken from it, as in SingleTaylorExpansionTerms. + var newPartialDerivative = partialDerivative.Differentiate(exprVariable).Simplify(); newTerms.Add((coefficient, newPointCoeffs, newPartialDerivative)); } @@ -146,6 +169,7 @@ internal static IEnumerable MultivariableTaylorExpansionTerms(Entity exp var solvedPartialDerivative = partialDerivative; foreach (var (exprVariable, polyVariable, point) in exprToPolyVars) solvedPartialDerivative = solvedPartialDerivative.Substitute(exprVariable, point); + solvedPartialDerivative = solvedPartialDerivative.Simplify(); var fullTerm = solvedPartialDerivative * coefficient * pointCoefficients; diff --git a/Sources/Tests/UnitTests/Calculus/SeriesTest.cs b/Sources/Tests/UnitTests/Calculus/SeriesTest.cs index 829b62585..9f1f76aba 100644 --- a/Sources/Tests/UnitTests/Calculus/SeriesTest.cs +++ b/Sources/Tests/UnitTests/Calculus/SeriesTest.cs @@ -17,7 +17,10 @@ public sealed class SeriesTest [InlineData("sin(t)", "(((0) + (x)) + ((0) + ((((-1) * ((x) ^ (3))) / ((3)!)) + (0)))) + (((((x) ^ (5)) / ((5)!)) + (0)) + ((((-1) * ((x) ^ (7))) / ((7)!)) + ((0) + (((x) ^ (9)) / ((9)!)))))", 10)] [InlineData("cos(t)", "(((1) + (0)) + ((((-1) * ((x) ^ (2))) / ((2)!)) + ((0) + (((x) ^ (4)) / ((4)!))))) + (((0) + (((-1) * ((x) ^ (6))) / ((6)!))) + ((0) + ((((x) ^ (8)) / ((8)!)) + (0))))", 10)] [InlineData("tan(t)", "((0) + ((x) + (0))) + ((((2) * ((x) ^ (3))) / ((3)!)) + ((0) + (((16) * ((x) ^ (5))) / ((5)!))))", 6)] - [InlineData("cotan(1 - t)", "(cotan(1)) + (((((-1) / ((sin(1)) ^ (2))) * (-1)) * (x)) + ((((((-1) * ((((2) * (sin(1))) * ((cos(1)) * (-1))) * (-1))) / (((sin(1)) ^ (2)) ^ (2))) * (-1)) * ((x) ^ (2))) / ((2)!)))", 3)] + // The derivatives are simplified as they are taken, so the coefficients are csc(1)^2 and + // 2 cot(1) csc(1)^2, where they were the unsimplified quotient rule's. + // https://github.com/asc-community/AngouriMath/issues/2291 + [InlineData("cotan(1 - t)", "(cotan(1)) + ((((1) / ((sin(1)) ^ (2))) * (x)) + ((((2) * ((cos(1)) / ((sin(1)) ^ (3)))) * ((x) ^ (2))) / ((2)!)))", 3)] [InlineData("a t3 + b t2 + c t + d", "((d) + ((c) * (x))) + (((((2) * (b)) * ((x) ^ (2))) / ((2)!)) + ((((6) * (a)) * ((x) ^ (3))) / ((3)!)))", 4)] public void TaylorDirect(string funcOverTRaw, string expectedRaw, int termCount) { @@ -31,7 +34,10 @@ public void TaylorDirect(string funcOverTRaw, string expectedRaw, int termCount) [InlineData("sin(t)", "(((0) + (x)) + ((0) + ((((-1) * ((x) ^ (3))) / ((3)!)) + (0)))) + (((((x) ^ (5)) / ((5)!)) + (0)) + ((((-1) * ((x) ^ (7))) / ((7)!)) + ((0) + (((x) ^ (9)) / ((9)!)))))", 10)] [InlineData("cos(t)", "(((1) + (0)) + ((((-1) * ((x) ^ (2))) / ((2)!)) + ((0) + (((x) ^ (4)) / ((4)!))))) + (((0) + (((-1) * ((x) ^ (6))) / ((6)!))) + ((0) + ((((x) ^ (8)) / ((8)!)) + (0))))", 10)] [InlineData("tan(t)", "((0) + ((x) + (0))) + ((((2) * ((x) ^ (3))) / ((3)!)) + ((0) + (((16) * ((x) ^ (5))) / ((5)!))))", 6)] - [InlineData("cotan(1 - t)", "(cotan(1)) + (((((-1) / ((sin(1)) ^ (2))) * (-1)) * (x)) + ((((((-1) * ((((2) * (sin(1))) * ((cos(1)) * (-1))) * (-1))) / (((sin(1)) ^ (2)) ^ (2))) * (-1)) * ((x) ^ (2))) / ((2)!)))", 3)] + // The derivatives are simplified as they are taken, so the coefficients are csc(1)^2 and + // 2 cot(1) csc(1)^2, where they were the unsimplified quotient rule's. + // https://github.com/asc-community/AngouriMath/issues/2291 + [InlineData("cotan(1 - t)", "(cotan(1)) + ((((1) / ((sin(1)) ^ (2))) * (x)) + ((((2) * ((cos(1)) / ((sin(1)) ^ (3)))) * ((x) ^ (2))) / ((2)!)))", 3)] [InlineData("a t3 + b s2 + c u + d", "((d) + ((c) * (z))) + (((((2) * (b)) * ((y) ^ (2))) / ((2)!)) + ((((6) * (a)) * ((x) ^ (3))) / ((3)!)))", 4)] [InlineData("(e^t)ln(1 + s)", "(0) + ((y) + ((((2) * ((x) * (y))) + ((-1) * ((y) ^ (2)))) / ((2)!)))", 3)] [InlineData("sin(t) + cos(s) + tan(u)", "((1) + ((x) + (z)))", 2)] @@ -45,6 +51,53 @@ public void MultivariableTaylorDirect(string funcOverTRaw, string expectedRaw, i actual.ShouldBe(expected); } + /// + /// Each derivative is simplified before the next is taken from it, so a quotient's + /// expansion stays the size of its terms. Taken as they stood, the derivatives of + /// sin(x) / x grew about eightfold a step: 7 terms around 1 printed in 36,421 + /// characters, and 11 ran out of memory. + /// https://github.com/asc-community/AngouriMath/issues/2291 + /// + [Theory] + [InlineData("sin(x) / x", 7)] + [InlineData("sin(x) / x", 11)] + [InlineData("e^x / (1 + x^2)", 7)] + public void AQuotientIsExpandedWithoutItsDerivativesGrowing(string func, int termCount) + { + Entity expr = func; + var taylor = MathS.Series.Taylor(expr, termCount, ("x", 1)); + Assert.True(taylor.Stringize().Length < 300 * termCount, taylor.Stringize()); + var expected = expr.Substitute("x", 1.1).EvalNumerical(); + var actual = taylor.Substitute("x", 1.1).EvalNumerical(); + var error = (expected - actual).Abs(); + Assert.True(error < 1e-6, $"{actual} where the function is {expected}"); + } + + /// + /// A function with no value at the point has no expansion there, nor has any of its + /// derivatives, so the expansion has none, once. It was the sum of the terms, each + /// NaN, after differentiating as many times as there were terms. + /// https://github.com/asc-community/AngouriMath/issues/2291 + /// + [Fact] + public void AnExpansionWhereTheFunctionHasNoValueHasNone() + { + Assert.Equal(Entity.Number.Real.NaN, MathS.Series.Taylor("sin(x) / x", 4, ("x", 0))); + Assert.Equal(Entity.Number.Real.NaN, MathS.Series.Taylor("sin(x) / x", 11, ("x", 0))); + Assert.Equal(Entity.Number.Real.NaN, MathS.Series.Taylor("sin(x) / x + y", 3, ("x", 0), ("y", 0))); + } + + /// + /// No terms add up to 0. It threw AngouriBugException, since the sum of no terms was + /// asked of a method that needs one. https://github.com/asc-community/AngouriMath/issues/2291 + /// + [Fact] + public void NoTermsAreZero() + { + Assert.Equal(Entity.Number.Integer.Zero, MathS.Series.Taylor("sin(x)", 0, ("x", 1))); + Assert.Equal(Entity.Number.Integer.Zero, MathS.Series.Taylor("sin(x) + y", 0, ("x", 1), ("y", 2))); + } + [Theory] [InlineData(0, 6, "sin(x)", -0.5)] [InlineData(0, 6, "sin(x)", -0.4)] diff --git a/Sources/Tests/UnitTests/Convenience/DocumentationExamplesRunTest.cs b/Sources/Tests/UnitTests/Convenience/DocumentationExamplesRunTest.cs index 81587a9c5..1ead75d21 100644 --- a/Sources/Tests/UnitTests/Convenience/DocumentationExamplesRunTest.cs +++ b/Sources/Tests/UnitTests/Convenience/DocumentationExamplesRunTest.cs @@ -105,10 +105,6 @@ public sealed class DocumentationExamplesRunTest "M:AngouriMath.MathS.Piecewise(System.Collections.Generic.IEnumerable{AngouriMath.Entity.Providedf},AngouriMath.Entity)", "M:AngouriMath.MathS.Piecewise(System.ValueTuple{AngouriMath.Entity,AngouriMath.Entity}[])", "M:AngouriMath.MathS.Pow(AngouriMath.Entity,AngouriMath.Entity)", - "M:AngouriMath.MathS.Series.Maclaurin(AngouriMath.Entity,System.Int32,AngouriMath.Entity.Variable[])", - "M:AngouriMath.MathS.Series.Taylor(AngouriMath.Entity,System.Int32,System.ValueTuple{AngouriMath.Entity.Variable,AngouriMath.Entity.Variable,AngouriMath.Entity}[])", - "M:AngouriMath.MathS.Series.Taylor(AngouriMath.Entity,System.Int32,System.ValueTuple{AngouriMath.Entity.Variable,AngouriMath.Entity}[])", - "M:AngouriMath.MathS.Series.TaylorTerms(AngouriMath.Entity,System.ValueTuple{AngouriMath.Entity.Variable,AngouriMath.Entity.Variable,AngouriMath.Entity}[])", "M:AngouriMath.MathS.Signum(AngouriMath.Entity)", "M:AngouriMath.MathS.SolveOde(AngouriMath.Entity,AngouriMath.Entity.Variable,AngouriMath.Entity.Variable)", "M:AngouriMath.MathS.Sqr(AngouriMath.Entity)",