From 29f2acdf628b60fb5477dbc9874a1f84a4117a5b Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 12 Oct 2026 12:04:54 +0000 Subject: [PATCH 1/3] A term integrated by parts takes the rest of the sum into its remainder A sum whose terms are not elementary apart is integrated by parts against the power of x in each term that has one, and what parts leaves is asked together with the other terms, read as powers of the sine and the cosine with the least powers taken out, or closed as the derivative of one such product. Rubi's 4.7.7 sqrt(csc(x)) (x cos(x) - 4 sec(x) tan(x)). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 220 ++++++++++++++++++ ...tegratedByPartsTakesTheRestOfTheSumTest.cs | 58 +++++ 2 files changed, 278 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ATermIntegratedByPartsTakesTheRestOfTheSumTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 0a1498898..ca8eb7bdc 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -99,6 +99,8 @@ internal static class IndefiniteIntegralSolver // corpus for nothing. if (!Integration.AnsweringTheQuestionAsked) return null; + if (integrateByParts && ByPartsAgainstThePowersOfX(splitted, x) is { } byPartsOverTheSum) + return byPartsOverTheSum; // What is polynomial in a term is what has no root of the variable in it: a // logarithm or an exponential of x is an indeterminate as far as gathering and // cancelling go. Bronstein's @@ -143,6 +145,224 @@ internal static class IndefiniteIntegralSolver }); } + /// + /// A sum whose terms are not elementary apart, integrated by parts against the power + /// of in each term that has one, with what parts leaves and the + /// other terms asked together as one integrand: x^k f + g is + /// x^k F + ∫(g - k x^(k - 1) F) for F the antiderivative of f. + /// + /// + /// + /// sqrt(csc(x)) (x cos(x) - 4 sec(x) tan(x)), Rubi's 4.7.7, is + /// (2 x sqrt(sin(x)) - 4 sin(x)^(3/2)/cos(x))'. Split into its two terms, neither is + /// elementary: parts on the first leaves -2 sqrt(sin(x)), and that and the second + /// term are elementary only together, as sqrt(sin(x)) (-2 - 4/cos(x)^2). So the + /// remainder is gathered into one quotient with the factors its terms share taken out, + /// which is the spelling the rules read; and where it is zero, as for + /// x cos(x) e^sin(x) + e^sin(x), the parts are the answer. + /// + /// + /// Only where some term with a power of beside something else in + /// has an elementary F, and only at the question asked: the + /// remainder is a new question, and the powers of of the terms + /// integrated are lower in it, so asking it again descends. + /// + /// + private static Entity? ByPartsAgainstThePowersOfX(IReadOnlyList terms, Entity.Variable x) + { + Entity parts = Number.Integer.Zero; + var remainder = new List(); + var any = false; + foreach (var term in terms) + { + var power = EInteger.Zero; + Entity rest = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(term)) + if (factor == x) + power = power.Add(EInteger.One); + else if (factor is Powf(var @base, Number.Integer { EInteger.Sign: > 0 } exponent) && @base == x) + power = power.Add(exponent.EInteger); + else + rest *= factor; + if (power.IsZero || !rest.ContainsNode(x)) + { + remainder.Add(term); + continue; + } + // A term whose cofactor is not elementary goes into the remainder as it is: in + // `x^2 cos(x) e^sin(x) + 2 x e^sin(x)` the second is what parts on the first leaves. + if (Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts: true) is not { } antiderivative) + { + remainder.Add(term); + continue; + } + var monomial = power.Equals(EInteger.One) ? (Entity)x : MathS.Pow(x, Number.Integer.Create(power)); + parts += monomial * antiderivative; + // No `x^0` for a first power: it would bring its `provided not x = 0` along. + var lower = power.Equals(EInteger.One) ? Number.Integer.One : MathS.Pow(x, Number.Integer.Create(power.Subtract(EInteger.One))); + remainder.Add(-Number.Integer.Create(power) * lower * antiderivative); + any = true; + } + if (!any) + return null; + var left = remainder.Aggregate((l, r) => l + r).InnerSimplified; + if (TreeAnalyzer.IsZero(left)) + return parts; + var gathered = OverTheLeastPowersOfTheSineAndTheCosine(left, x, out var closed); + if (closed is not null) + return parts + closed; + if (gathered is null) + { + var (above, below) = Functions.SingleQuotient.Of(left); + gathered = WithTheFactorsEveryTermSharesOut(above.InnerSimplified, x) / below; + } + return Integration.ComputeAsAQuestionOfItsOwn(gathered, x, integrateByParts: true) is { } integrated + ? parts + integrated + : null; + } + + /// + /// with each term read as a number times powers of + /// sin(u) and cos(u) for one argument u, and written as the least + /// power of each times the sum of what is left; where a term + /// is not such a product. + /// + /// + /// What parts leaves is spelled the way the antiderivative came back, and the other + /// terms the way they were written: (1/sin(x))^(-1/2) beside + /// sqrt(csc(x)) sec(x) tan(x) share nothing as written, and are + /// sqrt(sin(x)) and sqrt(sin(x))/cos(x)^2 as powers. Reading + /// (1/f)^p as f^(-p) holds wherever f is positive or p is + /// whole; where neither, the integrand is not real. + /// + /// And where the sum is the derivative of one such + /// product, which the rules otherwise answer in the half angle's tangent at four times + /// the size: (K sin(u)^(a + 1) cos(u)^(b + 1))' is + /// K u' sin(u)^a cos(u)^b ((a + b + 2) cos(u)^2 - (b + 1)), and + /// K u' sin(u)^a cos(u)^b ((a + 1) - (a + b + 2) sin(u)^2) the other way, so + /// sqrt(sin(x)) (-2 - 4/cos(x)^2) is (-4 sin(x)^(3/2)/cos(x))'. + /// + /// + private static Entity? OverTheLeastPowersOfTheSineAndTheCosine(Entity sum, Entity.Variable x, out Entity? antiderivative) + { + antiderivative = null; + Entity? argument = null; + var read = new List<(Entity Coefficient, Number.Rational Sine, Number.Rational Cosine)>(); + foreach (var term in TreeAnalyzer.GatherLinearChildrenOverSumAndExpand(sum, e => e.ContainsNode(x)) ?? new List { sum }) + if (Read(term) is { } powers) + read.Add(powers); + else + return null; + if (argument is null || read.Count < 2) + return null; + var leastSine = read.Select(r => r.Sine).Aggregate((l, r) => r < l ? r : l); + var leastCosine = read.Select(r => r.Cosine).Aggregate((l, r) => r < l ? r : l); + Entity Power(Entity @base, Number.Rational power) + => power == Number.Integer.Zero ? Number.Integer.One : power == Number.Integer.One ? @base : MathS.Pow(@base, power); + var slope = argument.Differentiate(x).InnerSimplified; + var two = Number.Integer.Create(2); + if (!slope.ContainsNode(x)) + { + Entity Total(System.Func<(Entity Coefficient, Number.Rational Sine, Number.Rational Cosine), bool> which) + => read.Where(which).Select(r => r.Coefficient).Aggregate((Entity)Number.Integer.Zero, (l, r) => l + r).InnerSimplified; + var (a, b) = (leastSine, leastCosine); + Entity? k = null; + if (read.All(r => r.Sine == a && (r.Cosine == b || r.Cosine == b + two)) && b + Number.Integer.One != Number.Integer.Zero) + { + var (squared, alone) = (Total(r => r.Cosine != b), Total(r => r.Cosine == b)); + var candidate = (-alone / ((b + Number.Integer.One) * slope)).InnerSimplified; + if (TreeAnalyzer.IsZero((squared - candidate * slope * (a + b + two)).InnerSimplified)) + k = candidate; + } + else if (read.All(r => r.Cosine == b && (r.Sine == a || r.Sine == a + two)) && a + Number.Integer.One != Number.Integer.Zero) + { + var (squared, alone) = (Total(r => r.Sine != a), Total(r => r.Sine == a)); + var candidate = (alone / ((a + Number.Integer.One) * slope)).InnerSimplified; + if (TreeAnalyzer.IsZero((squared + candidate * slope * (a + b + two)).InnerSimplified)) + k = candidate; + } + if (k is not null) + { + var (sinePower, cosinePower) = (a + Number.Integer.One, b + Number.Integer.One); + Entity product = k; + if (sinePower < Number.Integer.Zero) product /= Power(MathS.Sin(argument), -sinePower); else product *= Power(MathS.Sin(argument), sinePower); + if (cosinePower < Number.Integer.Zero) product /= Power(MathS.Cos(argument), -cosinePower); else product *= Power(MathS.Cos(argument), cosinePower); + antiderivative = product; + } + } + var left = read.Select(r => r.Coefficient * Power(MathS.Sin(argument), r.Sine - leastSine) * Power(MathS.Cos(argument), r.Cosine - leastCosine)) + .Aggregate((l, r) => l + r); + Entity above = left, below = Number.Integer.One; + if (leastSine < Number.Integer.Zero) below *= Power(MathS.Sin(argument), -leastSine); else above = Power(MathS.Sin(argument), leastSine) * above; + if (leastCosine < Number.Integer.Zero) below *= Power(MathS.Cos(argument), -leastCosine); else above = Power(MathS.Cos(argument), leastCosine) * above; + return below == Number.Integer.One ? above : above / below; + + (Entity Coefficient, Number.Rational Sine, Number.Rational Cosine)? Read(Entity node) + { + if (!node.ContainsNode(x)) + return (node, Number.Integer.Zero, Number.Integer.Zero); + switch (node) + { + case Sinf(var u): return Base(u, Number.Integer.One, Number.Integer.Zero); + case Cosf(var u): return Base(u, Number.Integer.Zero, Number.Integer.One); + case Secantf(var u): return Base(u, Number.Integer.Zero, Number.Integer.MinusOne); + case Cosecantf(var u): return Base(u, Number.Integer.MinusOne, Number.Integer.Zero); + case Tanf(var u): return Base(u, Number.Integer.One, Number.Integer.MinusOne); + case Cotanf(var u): return Base(u, Number.Integer.MinusOne, Number.Integer.One); + case Mulf(var l, var r): + return (Read(l), Read(r)) is ({ } a, { } b) ? ((a.Coefficient * b.Coefficient).InnerSimplified, a.Sine + b.Sine, a.Cosine + b.Cosine) : null; + case Divf(var l, var r): + return (Read(l), Read(r)) is ({ } n, { } d) ? ((n.Coefficient / d.Coefficient).InnerSimplified, n.Sine - d.Sine, n.Cosine - d.Cosine) : null; + case Powf(var @base, Number.Rational power) when Read(@base) is { } inner: + // A whole power of anything read; a fractional one only of a single + // power of the sine or of the cosine with nothing beside it, so that + // no root of a product is split. + if (power is Number.Integer) + return (MathS.Pow(inner.Coefficient, power).InnerSimplified, inner.Sine * power, inner.Cosine * power); + if (inner.Coefficient == Number.Integer.One && (inner.Sine == Number.Integer.Zero || inner.Cosine == Number.Integer.Zero)) + return (Number.Integer.One, inner.Sine * power, inner.Cosine * power); + return null; + default: + return null; + } + } + + (Entity Coefficient, Number.Rational Sine, Number.Rational Cosine)? Base(Entity u, Number.Rational sine, Number.Rational cosine) + { + if (argument is not null && argument != u) + return null; + argument = u; + return (Number.Integer.One, sine, cosine); + } + } + + /// + /// as the factors with in them that every + /// term has, as written, times the sum of what is left; as written where there are none. + /// + private static Entity WithTheFactorsEveryTermSharesOut(Entity sum, Entity.Variable x) + { + var terms = Sumf.LinearChildren(sum).Select(term => Mulf.LinearChildren(term).ToList()).ToList(); + if (terms.Count < 2) + return sum; + var shared = new List(); + foreach (var factor in terms[0]) + if (factor.ContainsNode(x) && terms.Skip(1).All(other => other.Contains(factor))) + { + shared.Add(factor); + foreach (var other in terms.Skip(1)) + other.Remove(factor); + } + if (shared.Count == 0) + return sum; + var firstLeft = new List(terms[0]); + foreach (var factor in shared) + firstLeft.Remove(factor); + Entity Product(List factors) => factors.Count == 0 ? Number.Integer.One : factors.Aggregate((l, r) => l * r); + var left = new[] { Product(firstLeft) }.Concat(terms.Skip(1).Select(Product)).Aggregate((l, r) => l + r); + return Product(shared) * left; + } + /// The largest term a sum is split over as written, before the expanding gather. private const int LargestTermTakenAsWritten = 60; diff --git a/Sources/Tests/UnitTests/Calculus/ATermIntegratedByPartsTakesTheRestOfTheSumTest.cs b/Sources/Tests/UnitTests/Calculus/ATermIntegratedByPartsTakesTheRestOfTheSumTest.cs new file mode 100644 index 000000000..ce6e7350d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ATermIntegratedByPartsTakesTheRestOfTheSumTest.cs @@ -0,0 +1,58 @@ +// +// 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 +{ + /// + /// A sum whose terms are not elementary apart, integrated by parts against the power of + /// x in a term, with what parts leaves asked together with the other terms: + /// sqrt(csc(x)) (x cos(x) - 4 sec(x) tan(x)) is + /// (2 x sqrt(sin(x)) - 4 sin(x)^(3/2)/cos(x))'. Rubi's 4.7.7. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ATermIntegratedByPartsTakesTheRestOfTheSumTest + { + [Theory] + [InlineData("sqrt(csc(x))*(x*cos(x) - 4*sec(x)*tan(x))", 100, "0.2 0.6 0.9 2.3")] + [InlineData("x*cos(x)/sqrt(sin(x)) - 4*sqrt(sin(x))/cos(x)^2", 100, "0.2 0.6 0.9 2.3")] + [InlineData("x*sin(x)/sqrt(cos(x)) + 4*sqrt(cos(x))/sin(x)^2", 100, "0.2 0.6 0.9 1.3")] + [InlineData("sqrt(csc(2*x + 1))*(x*cos(2*x + 1) - 2*sec(2*x + 1)*tan(2*x + 1))", 150, "0.2 0.35 0.6 0.9")] + [InlineData("x*cos(x)*e^sin(x) + e^sin(x)", 50, "0.35 1.1 2.3 4.4")] + [InlineData("x^2*cos(x)*e^sin(x) + 2*x*e^sin(x)", 50, "0.35 1.1 2.3 4.4")] + public void DifferentiatesBack(string integrand, int atMost, string points) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}"); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + foreach (var at in points.Split(' ')) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + Assert.True(Math.Abs((double)want.ImaginaryPart) < 1e-12, $"{integrand} is not real at x = {at}"); + var got = derivative.Substitute("x", at.ToEntity()).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + /// + /// What parts leaves is -2 sqrt(sin(x)), and beside 3 sqrt(sin(x)) it is + /// sqrt(sin(x)), which is not elementary: declined, not answered. + /// + [Fact] + public void ARemainderThatIsNotElementaryIsDeclined() + => Assert.Contains("integral(", "x*cos(x)/sqrt(sin(x)) + 3*sqrt(sin(x))".ToEntity().Integrate("x").Stringize()); + } +} From 044b739c6498225d756aab95e0f51cdcb0100276 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 12 Oct 2026 15:15:37 +0000 Subject: [PATCH 2/3] Bound integrating a sum by parts to one level and to transcendental cofactors Asked again inside its own questions, the rule hung (c + d x) erfc(a + b x)^2; beside an algebraic or rational cofactor it turned half-second declines into minutes. Only cofactors with x inside transcendental functions alone, and only where the antiderivative leaves no polynomial in x beside it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index ca8eb7bdc..2aedd1ee0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -169,6 +169,48 @@ internal static class IndefiniteIntegralSolver /// /// private static Entity? ByPartsAgainstThePowersOfX(IReadOnlyList terms, Entity.Variable x) + { + // Once: the cofactors and the remainder are asked at the top, where this runs again, + // and `(c + d x) erfc(a + b x)^2` went from a 13-second decline to a hang that way. + if (integratingASumByParts) + { + Integration.DeclinedForItsScope(); + return null; + } + integratingASumByParts = true; + try + { + return ByPartsAgainstThePowersOfXOnce(terms, x); + } + finally + { + integratingASumByParts = false; + } + } + + /// + /// Whether a term of has a factor that is a polynomial + /// in of positive degree. + /// + private static bool HasAPolynomialFactorIn(Entity antiderivative, Entity.Variable x) + => Sumf.LinearChildren(antiderivative).Any(term => Mulf.LinearChildren(term).Any(factor => + factor.ContainsNode(x) && TreeAnalyzer.TryGetPolynomial(factor, x, out var powers) + && powers.Keys.All(power => power.Sign >= 0) && powers.Values.All(coefficient => !coefficient.ContainsNode(x)))); + + /// + /// Whether every occurrence of in is inside + /// the argument of a function that is not algebraic: cos(x)/sqrt(sin(x)), not + /// sqrt(x^2 + 3) or atanh(x)/(1 - x^2). + /// + private static bool OnlyInsideTranscendentalFunctions(Entity expr, Entity.Variable x) + => !expr.ContainsNode(x) + || expr is not (Sumf or Minusf or Mulf or Divf or Powf or Entity.Variable or Number) + || expr is not Entity.Variable && expr.DirectChildren.All(child => OnlyInsideTranscendentalFunctions(child, x)); + + /// Set while asks its questions. + [System.ThreadStatic] private static bool integratingASumByParts; + + private static Entity? ByPartsAgainstThePowersOfXOnce(IReadOnlyList terms, Entity.Variable x) { Entity parts = Number.Integer.Zero; var remainder = new List(); @@ -184,14 +226,24 @@ internal static class IndefiniteIntegralSolver power = power.Add(exponent.EInteger); else rest *= factor; - if (power.IsZero || !rest.ContainsNode(x)) + // Only a cofactor with x in it nowhere but inside transcendental functions. A power + // of x beside an algebraic one is what the algebraic reductions answer, and asking + // each such cofactor and the remainder took `(x^2 + 3)^2/(x + sqrt(x^2 + 3))` from a + // half-second decline past two minutes; and beside a rational function of x as + // well, `x^3 atanh(c x^2)/(1 - c^2 x^4)` in what parts leaves of + // `(a + b atanh(c x^2))^2/x^5`, the cofactor is a slow decline that doubled its time. + if (power.IsZero || !rest.ContainsNode(x) || !OnlyInsideTranscendentalFunctions(rest, x)) { remainder.Add(term); continue; } // A term whose cofactor is not elementary goes into the remainder as it is: in // `x^2 cos(x) e^sin(x) + 2 x e^sin(x)` the second is what parts on the first leaves. - if (Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts: true) is not { } antiderivative) + // And one with a polynomial in x beside something in its antiderivative: what parts + // leaves then has the power it took away, and asking it does not descend -- + // `x erfc(a + b x)^2`, whose cofactor's antiderivative has `x erfc(a + b x)^2` in it. + if (Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts: true) is not { } antiderivative + || HasAPolynomialFactorIn(antiderivative, x)) { remainder.Add(term); continue; From 0816c04cebdbdc5acec892bdc481c8f06c374548 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 12 Oct 2026 17:03:36 +0000 Subject: [PATCH 3/3] Breaking-changes entry for a sum integrated by parts against its powers of x Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 4093449e2..422854f4c 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -614,6 +614,22 @@ polynomial in the symbols is written as the two quadratics first | `"cot(e + f*x)^3*sqrt(a + b*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 36,760 characters, a case for each sign of the roots | | `"sec(e + f*x)^2/(a*sin(e + f*x) + b*tan(e + f*x))^2".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 68,854 characters | +### A term with a power of `x` is integrated by parts with the rest of the sum in its remainder + +**Answers where there were none.** `sqrt(csc(x)) (x cos(x) - 4 sec(x) tan(x))`, Rubi's 4.7.7, was declined: neither +of its two terms is elementary alone. Where a sum's terms are not integrated one by one, a term `x^k f` whose `f` +has `x` only inside transcendental functions is now integrated by parts, and what parts leaves is asked together +with the other terms. Here that is `-2 sqrt(sin(x)) - 4 sqrt(sin(x))/cos(x)^2`, a power of the sine over a power of +the cosine, which is integrated in closed form +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(csc(x))*(x*cos(x) - 4*sec(x)*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | `x * -(1 / sin(x)) ^ (-1/2) / (-1/2) + (-4) * sin(x) ^ (3/2) / cos(x)` | +| `"x*sin(x)/sqrt(cos(x)) + 4*sqrt(cos(x))/sin(x)^2".ToEntity().Integrate("x")` | `integral(...)` | `x * -sqrt(cos(x)) / (1/2) + (-4) / sin(x) * cos(x) ^ (3/2)` | +| `"x^2*cos(x)*e^sin(x) + 2*x*e^sin(x)".ToEntity().Integrate("x")` | `integral(...)` | `x ^ 2 * e ^ sin(x)` | +| `"(c + d*x)*erfc(a + b*x)^2".ToEntity().Integrate("x")` | 187 characters, reading `erfc` as a symbol; `integral(...)` on the unreleased master | 1,196 characters | + ### An odd power of the tangent beside a function of the secant is integrated in the secant **Answers where there were none.** `cot(c + d x)^3/(a + b sec(c + d x))^(3/2)` ran past a minute. In `u = sec(y)`,