From 483fe70751dbe755e919afad22eae78aa98116e0 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 20 Oct 2026 22:11:49 +0000 Subject: [PATCH] Read a derivative of a product with a sine, a cosine or an exponential raised from nothing in it Fifteen of Rubi's 4.7.6, 4.7.7, 6.1.4, 6.2.4 and 6.7.1 are exact derivatives, or one beside an integral that is answered, written out: - The derivative of a product of powers may raise a sine, a cosine or an exponential from nothing, one at a time, and a power with x in its base and exponent is read with its exponent's derivative times its base's logarithm: cosh(x)^x (ln(cosh(x)) + x tanh(x)) is (cosh(x)^x)'. - Where the brackets are not proportional, the integrand may be a constant times that derivative and a constant times the functions alone. - A denominator with a negative exponential in it is multiplied above and below by the positive one. - cos(u)^2 + sin(u)^2 in a sum is written as 1. - A polynomial factor that is a constant multiple of a substitution's du/dx is divided out. Rubi pocket (1,901 rows): 1829 -> 1844 solved. The moves re-run alone gain 15 rows (4.7.6:53-55, 4.7.7:90, 977, 1180, 1199, 1201, 6.1.4:14, 19, 6.2.4:14, 19, 6.7.1:634, 766, 1252), none lost; 3.2.1:292 allocates the same on both builds at a 60 s budget. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 32 ++ .../Integration/IndefiniteIntegralSolver.cs | 515 +++++++++++++++++- .../Integration/Integration.Definition.cs | 12 +- ...hAFunctionRaisedFromNothingIntegralTest.cs | 87 +++ 4 files changed, 616 insertions(+), 30 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AnExactDerivativeWithAFunctionRaisedFromNothingIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1d74c5ca3..c1108680e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -2566,6 +2566,38 @@ is written so, after the substitutions, and the factor goes in front of the answ | `"(1/(1 - x^2))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(1 - x^2) x/sqrt(1 - x^2)` | | `"(c/(a + b*x^2))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `(c/(a + b x^2))^(3/2) (a + b x^2)^(3/2) x/(a sqrt(a + b x^2))` | +### A derivative of a product is read with a sine, a cosine or an exponential raised from nothing in it + +**Answers where there were none.** Thirteen of Rubi's 4.7.6, 4.7.7, 6.1.4, 6.2.4 and 6.7.1 were declined, each an +exact derivative, or one beside an integral that is answered, written out +([#233](https://github.com/asc-community/AngouriMath/issues/233)): +- The derivative of a product of powers may raise a sine, a cosine or an exponential from nothing, one at a time: + `F^(c (a + b x)) (f x)^m (e x cos(d + e x) + (m + b c x ln F) sin(d + e x))/x` is + `(F^(c (a + b x)) (f x)^m sin(d + e x))'`. A power with x in its base and in its exponent is read with its exponent's + derivative times its base's logarithm, so `cosh(x)^x (ln(cosh(x)) + x tanh(x))` is `(cosh(x)^x)'`. +- Where the brackets are not proportional, the integrand may be a constant times that derivative and a constant times + the functions alone: `sinh(Q)/x^2 - b cosh(Q)/x` is `(-sinh(Q)/x)' + 2 c cosh(Q)` for `Q = a + b x + c x^2`, and the + second term is answered in error functions. +- A denominator with a negative exponential in it is multiplied above and below by the positive one: + `(cos(x) + sin(x))/(e^(-x) + sin(x))` is the logarithmic derivative of `1 + e^x sin(x)`. +- `cos(u)^2 + sin(u)^2` in a sum is written as `1`, so `e^(cos(x)^2 + sin(x)^2)` is `e`. +- A polynomial factor that is a constant multiple of a substitution's `du/dx` is divided out: + `(x + b/(2 c)) tan(a + b x + c x^2)` is `-ln(cos(a + b x + c x^2))/(2 c)`. + +Two more of 4.7.6, `F^(c (a + b x)) (e x cos(d + e x) + (b c x ln F - 1) sin(d + e x))/x^2` and its `x^3` twin, were +declined by 2.5.0 and answered by the unreleased master in exponential integrals, 1,209 and 2,331 characters; they are +the product `F^(c (a + b x)) sin(d + e x)/x` and its twin now, 50 and 53. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"g^(c*(a+b*x))*(f*x)^m*(k*x*cos(d+k*x)+(m+b*c*x*ln(g))*sin(d+k*x))/x".ToEntity().Integrate("x")` | `integral(...)` | 78 characters | +| `"-b*cosh(a+b*x+c*x^2)/x+sinh(a+b*x+c*x^2)/x^2".ToEntity().Integrate("x")` | `integral(...)` | 294 characters | +| `"cosh(x)^x*(ln(cosh(x))+x*tanh(x))".ToEntity().Integrate("x")` | `integral(...)` | `((e^x + e^(-x))/2)^x` | +| `"(cos(x)+sin(x))/(1/e^x+sin(x))".ToEntity().Integrate("x")` | `integral(...)` | `ln(1 + sin(x) e^x)` | +| `"e^(cos(x)^2+sin(x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `e x` | +| `"1/2*b*tan(a+b*x+c*x^2)/c+x*tan(a+b*x+c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 45 characters | +| `"g^(c*(a+b*x))*(k*x*cos(d+k*x)+(-1+b*c*x*ln(g))*sin(d+k*x))/x^2".ToEntity().Integrate("x")` | `integral(...)` | 50 characters, where the unreleased master gave 1,209 | + ### A sum that is the derivative of a product of powers is read as one **Answers where there were none.** `m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1) (a + b x^n)^(p - 1)` is diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 234ced3fb..c83c16d6a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -16320,8 +16320,15 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L) } if (terms.Count < 2) return null; - var bases = terms[0].Powers.Keys.ToList(); - if (bases.Count == 0 || terms.Any(term => term.Powers.Count != bases.Count || bases.Any(@base => !term.Powers.ContainsKey(@base)))) + // The bases in every term; a function in some of them only -- a sine or cosine, an + // exponential or a sum of them -- stays in the bracket, where the rule raises it from + // nothing: `x^4/(b sqrt(R)) + x^2 cos(a + b x)/sqrt(R) + 4 x sqrt(R)/(3 b)`, for + // `R = x^3 + 3 sin(a + b x)`, is `x/sqrt(R)` times a bracket with the cosine in it. + var bases = terms[0].Powers.Keys.Where(@base => terms.All(term => term.Powers.ContainsKey(@base))).ToList(); + if (bases.Count == 0) + return null; + var functionsInSomeTerms = terms.Any(term => term.Powers.Count != bases.Count); + if (functionsInSomeTerms && terms.Any(term => term.Powers.Any(pair => !bases.Contains(pair.Key) && (pair.Value != Number.Integer.One || !IsAFunctionToRaise(pair.Key, x))))) return null; // For each base the least of its exponents, against which every other is more by a // whole number or by a sum of symbols with no sign in front: `x^(m - 1)` beside @@ -16348,7 +16355,7 @@ static bool More(Entity difference) return null; least[@base] = lowest; } - if (least.Values.All(exponent => exponent is Number.Integer)) + if (!functionsInSomeTerms && least.Values.All(exponent => exponent is Number.Integer)) return null; Entity bracket = Number.Integer.Zero; foreach (var (constant, powers) in terms) @@ -16357,6 +16364,9 @@ static bool More(Entity difference) foreach (var @base in bases) if (Difference(powers[@base], least[@base]) is var raised && raised != Number.Integer.Zero) rest = rest * (raised == Number.Integer.One ? @base : MathS.Pow(@base, raised)); + foreach (var function in powers.Keys) + if (!bases.Contains(function)) + rest = rest * function; bracket = bracket == Number.Integer.Zero ? rest : bracket + rest; } Entity common = Number.Integer.One; @@ -16554,9 +16564,24 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R /// then a constant times its own raised product's derivative -- and x may be raised from /// nothing, a power of it the integrand does not have. /// + /// + /// A function inside the bracket -- a sine, a cosine, an exponential or a sum of them, as + /// sinh is written -- may be raised from nothing too, one at a time: Rubi's 4.7.6 + /// F^(c (a + b x)) (f x)^m (e x cos(d + e x) + (m + b c x ln F) sin(d + e x))/x is + /// (F^(c (a + b x)) (f x)^m sin(d + e x))', and a sum whose terms have such a function + /// in some of them only has the powers common to all taken out and the function left in the + /// bracket. A power whose base and exponent both hold x is kept as the exponentials are, + /// with the derivative of its exponent times its base's logarithm, and the candidate may then + /// be the powers as they are: cosh(x)^x (ln(cosh(x)) + x tanh(x)). And where the + /// brackets are not proportional, the integrand may be a constant times the derivative and a + /// constant times the function alone, whose integral is asked: + /// sinh(Q)/x^2 - b cosh(Q)/x is (-sinh(Q)/x)' + 2 c cosh(Q) for + /// Q = a + b x + c x^2, where splitting the sum spent the budget declining both terms. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// /// https://github.com/asc-community/AngouriMath/issues/718 /// - internal static Entity? SolveAsTheDerivativeOfAProductOfPowers(Entity expr, Entity.Variable x) + internal static Entity? SolveAsTheDerivativeOfAProductOfPowers(Entity expr, Entity.Variable x, bool integrateByParts) { if (!Integration.AnsweringTheQuestionAskedOrOneBelow) return null; @@ -16566,13 +16591,13 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R // the derivative of `x^m (a + b x^n)^p`. As a sum it was split, and neither term is // elementary. if (expr is Sumf or Minusf) - return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x) : null; + return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x, integrateByParts) : null; // A bracket that is a polynomial with a power of x in every term has that power taken out, to // join the powers it multiplies: `x^(2n) (c + d x)^n (2 c x + 3 d x^2)` is // `x^(2n + 1) (c + d x)^n (2 c + 3 d x)`, the derivative of `x^(2n + 2) (c + d x)^(n + 1)/(n + 1)`, // and with the x in the bracket nothing raised x by enough. if (WithAPowerOfXOutOfTheBracket(expr, x) is { } outOfTheBracket) - return SolveAsTheDerivativeOfAProductOfPowers(outOfTheBracket, x); + return SolveAsTheDerivativeOfAProductOfPowers(outOfTheBracket, x, integrateByParts); Entity constant = Number.Integer.One; Entity? bracket = null; var raisable = new List<(Entity Base, Entity Exponent)>(); @@ -16617,9 +16642,27 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R kept = underneath ? kept / factor : kept * factor; continue; } + // A power with x in its base and its exponent is the exponential of the exponent + // times the base's logarithm, kept as the exponentials are, with that derivative: + // `cosh(x)^x (ln(cosh(x)) + x tanh(x))` is `(cosh(x)^x)'`, and raised it was squared. + if (factor is Powf(var variableBase, var variableExponent) && variableBase.ContainsNode(x) && variableExponent.ContainsNode(x)) + { + var derivative = Functions.PartialFractions.Bare((variableExponent * MathS.Ln(variableBase)).Differentiate(x).InnerSimplified); + logarithmicDerivativesKept.Add(underneath ? -derivative : derivative); + kept = underneath ? kept / factor : kept * factor; + continue; + } raisable.Add((factor, underneath ? Number.Integer.MinusOne : Number.Integer.One)); } - if (raisable.Count == 0 || raisable.Count > 4) + // A function inside the bracket, raised from nothing: the bracket of `(K sin(u))'` is + // `K'/K sin(u) + u' cos(u)`, so Rubi's + // `F^(c (a + b x)) (f x)^m (e x cos(d + e x) + (m + b c x ln F) sin(d + e x))/x` is + // `(F^(c (a + b x)) (f x)^m sin(d + e x))'`, and the sine was in no power to raise. + // At the question asked only: one below, where by parts and the substitutions ask it + // of every bracket they write, the candidates were a second of a decline. + var kernels = bracket is null || !Integration.AnsweringTheQuestionAsked ? new List() : KernelsOfTheBracket(bracket, raisable, x); + var variablePowerKept = logarithmicDerivativesKept.Any(derivative => derivative.ContainsNode(x)); + if (raisable.Count > 4 || raisable.Count == 0 && kernels.Count == 0 && !variablePowerKept) return null; // Where a power is not a whole one -- a root, or a symbolic exponent -- two more // candidates, each a shape nothing else reads. With no sum beside two or more powers, @@ -16639,8 +16682,29 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R } if (bracket is null) return null; - for (var subset = 1; subset < 1 << raisable.Count; subset++) + // The functions last, one at a time: two raised together is a product nothing writes. + var firstKernel = raisable.Count; + raisable.AddRange(kernels.Select(kernel => (kernel, (Entity)Number.Integer.Zero))); + var besideAFunction = new List<(Entity BracketOfTheDerivative, Entity Candidate)>(); + // With a power of a variable base kept, the candidate may be the integrand's powers as + // they are, nothing raised. + for (var subset = variablePowerKept ? 0 : 1; subset < 1 << raisable.Count; subset++) { + var kernelsRaised = 0; + var powersRaised = 0; + for (var i = 0; i < raisable.Count; i++) + if ((subset & (1 << i)) != 0) + { + if (i >= firstKernel) + kernelsRaised++; + else + powersRaised++; + } + // A function raised beside one power at most: the derivative of the function brings + // its partner in, times the other raised bases, and that is the coefficient of the + // partner in the bracket, a power of x or of one base in every shape read. + if (kernelsRaised > 1 || kernelsRaised == 1 && powersRaised > 1) + continue; Entity candidate = kept; Entity productOfRaisedBases = Number.Integer.One; for (var i = 0; i < raisable.Count; i++) @@ -16678,11 +16742,18 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R theOtherRaisedBases *= raisable[j].Base; bracketOfTheDerivative += newExponent * @base.Differentiate(x) * theOtherRaisedBases; } - if (candidate == kept) + if (subset != 0 && candidate == kept || !candidate.ContainsNode(x)) continue; bracketOfTheDerivative = Functions.PartialFractions.Bare(bracketOfTheDerivative.InnerSimplified); if (!AreProportionalAtSampledPoints(bracket, bracketOfTheDerivative, x)) + { + // Kept for after the exact candidates, which give the one product where + // there is one: `F^(c (a + b x)) (e cos(d + e x) + b c ln(F) sin(d + e x))` + // with the cosine raised leaves a sine, and is `(F^(c (a + b x)) sin(d + e x))'`. + if (kernelsRaised == 1) + besideAFunction.Add((bracketOfTheDerivative, candidate)); continue; + } // The constant is the quotient of one monomial's coefficients, the two // brackets read as polynomials in x and in the transcendental atoms holding // it -- `ln(d x)` -- each an indeterminate; or, where they do not read so, @@ -16693,6 +16764,11 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R if (k is Providedf(var inner, _)) k = inner; } + // With no symbol but x in the brackets, a quotient the simplifier cannot reduce -- + // `ln(cosh(x)) + x tanh(x)` over the same in exponentials -- is the number it takes + // at a point, where that is a small rational. + if (k.ContainsNode(x) && ASmallRationalRatioAtAPoint(bracket, bracketOfTheDerivative, x) is { } ratio) + k = ratio; k = Functions.PartialFractions.Bare((constant * k).InnerSimplified); if (k.ContainsNode(x) || k.Nodes.Any(node => node == MathS.NaN)) continue; @@ -16703,9 +16779,177 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R if (holds) return answer; } + foreach (var (bracketOfTheDerivative, candidate) in besideAFunction) + if (TheDerivativeBesideAFunctionOfTheBracket(expr, x, integrateByParts, constant, kept, raisable.Take(firstKernel).ToList(), bracket, bracketOfTheDerivative, candidate) is { } answer) + return answer; + return null; + } + + /// + /// The quotient of by at a point, as a + /// rational of denominator at most twelve within 1e-12 of it, where neither holds a symbol + /// but ; null otherwise. The caller checks it at sampled points. + /// + private static Entity? ASmallRationalRatioAtAPoint(Entity left, Entity right, Entity.Variable x) + { + if (left.Vars.Concat(right.Vars).Any(symbol => symbol != x)) + return null; + using var _ = MathS.Settings.DowncastingEnabled.Set(false); + foreach (var at in new[] { "0.29", "1.43" }) + { + var point = Number.Real.Create(EDecimal.FromString(at)); + if (left.Substitute(x, point).Evaled is not Number.Complex l || right.Substitute(x, point).Evaled is not Number.Complex r + || l.IsNaN || r.IsNaN || r.Abs().EDecimal.CompareTo(EDecimal.FromString("1e-30")) < 0) + continue; + var ratio = l / r; + if (ratio.ImaginaryPart.EDecimal.Abs().CompareTo(EDecimal.FromString("1e-12")) > 0) + return null; + var value = ratio.RealPart.EDecimal.ToDouble(); + for (var denominator = 1; denominator <= 12; denominator++) + { + var numerator = System.Math.Round(value * denominator); + if (System.Math.Abs(value - numerator / denominator) <= 1e-12 * System.Math.Max(1, System.Math.Abs(value))) + return Number.Rational.Create(EInteger.FromInt64((long)numerator), EInteger.FromInt32(denominator)); + } + return null; + } + return null; + } + + /// + /// The functions of the terms of are + /// products of, for to raise from + /// nothing: a sine, a cosine, or a sum of exponentials, sines and cosines -- `e^u - e^(-u)`, + /// which is how `2 sinh(u)` is written -- that is not among the powers already; none where + /// there are more than four, or where no term has x beside its functions. + /// + private static List KernelsOfTheBracket(Entity bracket, List<(Entity Base, Entity Exponent)> raisable, Entity.Variable x) + { + var kernels = new List(); + var aCoefficientHoldsX = false; + foreach (var term in Sumf.LinearChildren(bracket)) + { + var functions = 0; + var other = false; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(term)) + { + if (!underneath && IsAFunctionToRaise(factor, x)) + { + functions++; + if (!kernels.Contains(factor) && !raisable.Any(pair => pair.Base == factor)) + kernels.Add(factor); + } + else if (factor.ContainsNode(x)) + other = true; + } + aCoefficientHoldsX |= functions > 0 && other; + } + // A bracket of the functions with constant coefficients only is a sum the rules for the + // functions answer, or the angle sum of one -- `sin(d) cos(k x) + cos(d) sin(k x)`, which + // the search writes of every sine and asks of again, a second of candidates to decline. + return aCoefficientHoldsX && kernels.Count <= 4 ? kernels : new List(); + } + + /// + /// Whether is a sine or cosine of something with + /// in it, an exponential of it, or a sum of those times constants. + /// + private static bool IsAFunctionToRaise(Entity factor, Entity.Variable x) + { + if (!factor.ContainsNode(x)) + return false; + if (factor is Sinf or Cosf || factor is Powf(var @base, var exponent) && !@base.ContainsNode(x) && exponent.ContainsNode(x)) + return true; + return factor is Sumf or Minusf && Sumf.LinearChildren(factor).All(term => + term.ContainsNode(x) && FactorsOfTheIntegrand(term).All(pair => !pair.Factor.ContainsNode(x) + || !pair.Underneath && (pair.Factor is Sinf or Cosf || pair.Factor is Powf(var b, var e) && !b.ContainsNode(x) && e.ContainsNode(x)))); + } + + /// + /// For , where the brackets are not + /// proportional: the integrand as a constant times the candidate's derivative and what is + /// left, where that is the functions alone, asked as a question of its own. + /// sinh(Q)/x^2 - b cosh(Q)/x, for Q = a + b x + c x^2, is + /// (-sinh(Q)/x)' + 2 c cosh(Q), the error functions'. + /// + /// + /// The brackets are read as polynomials in x and in the functions in them, the constant is + /// tried off each monomial the two share, and what is left must keep no power of x: the + /// powers beside the bracket are x's alone and each monomial left has the degree that + /// cancels it. Linearity split Rubi's 6.1.4 and 6.2.4 into two terms, neither elementary, + /// and spent the budget declining them. At the question asked only, not re-entered, and + /// checked at sampled points before it is given. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + private static Entity? TheDerivativeBesideAFunctionOfTheBracket(Entity expr, Entity.Variable x, bool integrateByParts, + Entity constant, Entity kept, List<(Entity Base, Entity Exponent)> powers, Entity bracket, Entity bracketOfTheDerivative, Entity candidate) + { + if (!Integration.AnsweringTheQuestionAsked || derivativeBesideAFunction) + return null; + Entity besideTheBracket = Number.Integer.Zero; + foreach (var (@base, exponent) in powers) + { + if (exponent.Evaled is Number.Complex { IsZero: true }) + continue; + if (@base != x) + return null; + besideTheBracket = exponent; + } + if (!TryReadInAtoms(bracket, bracketOfTheDerivative, x, out var leftRead, out var rightRead, out var atoms) + || atoms.Any(pair => !IsAFunctionToRaise(pair.Atom, x))) + return null; + var comparer = new IntArrayComparer(); + var tried = new List(); + foreach (var pair in rightRead) + { + if (pair.Value.Evaled is Number.Complex { IsZero: true } || !leftRead.TryGetValue(pair.Key, out var above)) + continue; + var k = Functions.PartialFractions.Bare((above / pair.Value).Simplify()); + if (k.ContainsNode(x) || k.Nodes.Any(node => node == MathS.NaN) || tried.Contains(k)) + continue; + tried.Add(k); + var left = new List(); + var keepsAPower = false; + foreach (var key in leftRead.Keys.Concat(rightRead.Keys).Distinct(comparer)) + { + var coefficient = (leftRead.TryGetValue(key, out var l) ? l : Number.Integer.Zero) + - k * (rightRead.TryGetValue(key, out var r) ? r : Number.Integer.Zero); + if (VanishesIdentically(coefficient)) + continue; + if ((besideTheBracket + key[0]).InnerSimplified.Evaled is not Number.Complex { IsZero: true }) + { + keepsAPower = true; + break; + } + Entity term = coefficient; + for (var j = 1; j < key.Length; j++) + if (key[j] != 0) + term *= key[j] == 1 ? atoms[j - 1].Atom : MathS.Pow(atoms[j - 1].Atom, key[j]); + left.Add(term); + } + if (keepsAPower || left.Count == 0 || left.Count >= leftRead.Count) + continue; + var rest = (constant * kept * left.Aggregate((a, b) => a + b)).InnerSimplified; + Entity? integral; + derivativeBesideAFunction = true; + try + { + integral = Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts); + } + finally + { + derivativeBesideAFunction = false; + } + if (integral is null || integral.Nodes.Any(node => node is Integralf || node == MathS.NaN)) + return null; + var answer = Functions.PartialFractions.Bare((Functions.PartialFractions.Bare((constant * k * candidate).InnerSimplified) + integral).InnerSimplified); + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } return null; } + /// Set while asks its question. + [System.ThreadStatic] private static bool derivativeBesideAFunction; /// /// A binomial differential x^m (a + b x^n)^p with a symbol in its exponents, in @@ -16955,25 +17199,7 @@ static Entity Simplified(Entity exponent) private static bool TryReadTheRatioOfBrackets(Entity left, Entity right, Entity.Variable x, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? k) { k = null; - var atoms = new List<(Entity Atom, Entity.Variable Symbol)>(); - Entity everything = left + right; - Entity InAtoms(Entity entry) => entry.Replace(node => - { - if (!node.ContainsNode(x) || node == x || node is Sumf or Minusf or Mulf or Divf || node is Powf(_, Number.Integer { EInteger.Sign: >= 0 })) - return node; - foreach (var (atom, symbol) in atoms) - if (atom == node) - return symbol; - var fresh = Variable.CreateUnique(everything, "t_atom"); - atoms.Add((node, fresh)); - everything += fresh; - return fresh; - }); - var leftInAtoms = InAtoms(left); - var rightInAtoms = InAtoms(right); - var variables = new List { x }; - variables.AddRange(atoms.Select(pair => pair.Symbol)); - if (!TryReadInAll(leftInAtoms, variables, out var leftRead) || !TryReadInAll(rightInAtoms, variables, out var rightRead)) + if (!TryReadInAtoms(left, right, x, out var leftRead, out var rightRead, out _)) return false; // A monomial whose coefficient on the right is a number first, so that the // constant is the left coefficient over it and not a quotient of symbols to be @@ -16991,6 +17217,38 @@ Entity InAtoms(Entity entry) => entry.Replace(node => return false; } + /// + /// and read as polynomials in + /// and in every atom holding it -- a logarithm, an exponential -- + /// as an indeterminate, keyed by the powers of x and then of the atoms in order; false + /// where either does not read so. + /// + private static bool TryReadInAtoms(Entity left, Entity right, Entity.Variable x, + out Dictionary leftRead, out Dictionary rightRead, out List<(Entity Atom, Entity.Variable Symbol)> atoms) + { + var found = new List<(Entity Atom, Entity.Variable Symbol)>(); + atoms = found; + Entity everything = left + right; + Entity InAtoms(Entity entry) => entry.Replace(node => + { + if (!node.ContainsNode(x) || node == x || node is Sumf or Minusf or Mulf or Divf || node is Powf(_, Number.Integer { EInteger.Sign: >= 0 })) + return node; + foreach (var (atom, symbol) in found) + if (atom == node) + return symbol; + var fresh = Variable.CreateUnique(everything, "t_atom"); + found.Add((node, fresh)); + everything += fresh; + return fresh; + }); + var leftInAtoms = InAtoms(left); + var rightInAtoms = InAtoms(right); + var variables = new List { x }; + variables.AddRange(found.Select(pair => pair.Symbol)); + rightRead = new Dictionary(); + return TryReadInAll(leftInAtoms, variables, out leftRead) && TryReadInAll(rightInAtoms, variables, out rightRead); + } + /// /// Whether is a constant multiple of , /// decided at sampled points with every symbol pinned; false where fewer than two @@ -32457,6 +32715,139 @@ static bool Through(Entity node, Entity.Variable x) return Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts); } + /// + /// A quotient whose denominator is a sum with a negative exponential in a term, multiplied + /// above and below by the positive one: Rubi's (cos(x) + sin(x))/(e^(-x) + sin(x)) is + /// e^x (cos(x) + sin(x))/(1 + e^x sin(x)), the logarithmic derivative of + /// 1 + e^x sin(x). + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// Written with e^(-x) the numerator is the denominator's derivative plus the + /// denominator, and nothing reads that sum: it was declined. Only where the denominator + /// holds a function of x besides exponentials, since a rational function of exponentials + /// is the exponential substitution's; at the question asked, not re-entered, and checked at + /// sampled points. + /// + internal static Entity? SolveByClearingANegativeExponentialBelowTheBar(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked || clearingANegativeExponential) + return null; + // As written: the single quotient takes `1/e^x` up, and that is the rewriting this is. + Entity above = Number.Integer.One; + Entity? below = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!underneath) + above *= factor; + else if (below is null && factor is Sumf or Minusf) + below = factor; + else + return null; + } + if (below is null || !above.ContainsNode(x)) + return null; + Entity? @base = null, power = null; + var anotherFunction = false; + var terms = Sumf.LinearChildren(below).ToList(); + foreach (var term in terms) + foreach (var (factor, underneath) in FactorsOfTheIntegrand(term)) + { + if (!factor.ContainsNode(x)) + continue; + if (factor is not Powf(var b, var exponent) || b.ContainsNode(x) || !exponent.ContainsNode(x)) + { + anotherFunction = true; + continue; + } + if (@base is null && underneath != exponent is Mulf(Number.Real { IsNegative: true }, _)) + (@base, power) = (b, underneath ? exponent : (-exponent).InnerSimplified); + } + if (@base is null || power is null || !anotherFunction) + return null; + // Each term times base^power, its own power of the base joined to that one. + Entity Times(Entity term) + { + Entity rest = Number.Integer.One; + Entity exponentOfTheBase = power; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(term)) + { + if (factor is Powf(var b, var exponent) && b == @base && exponent.ContainsNode(x)) + exponentOfTheBase = underneath ? exponentOfTheBase - exponent : exponentOfTheBase + exponent; + else + rest = underneath ? rest / factor : rest * factor; + } + // Simplified, not inner-simplified, which leaves `x - x` as written. + exponentOfTheBase = Functions.PartialFractions.Bare(exponentOfTheBase.Simplify()); + return exponentOfTheBase.Evaled is Number.Complex { IsZero: true } ? rest : rest * MathS.Pow(@base, exponentOfTheBase); + } + var cleared = terms.Select(Times).Aggregate((left, right) => left + right); + var rewritten = (Times(above) / cleared).InnerSimplified; + if (rewritten == expr || rewritten == expr.InnerSimplified) + return null; + Entity? answer; + clearingANegativeExponential = true; + try + { + answer = Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); + } + finally + { + clearingANegativeExponential = false; + } + return answer is not null && Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + + /// Set while asks its question. + [System.ThreadStatic] private static bool clearingANegativeExponential; + + /// + /// A squared sine and a squared cosine of one argument in a sum, written as the one they + /// add up to: Rubi's e^(cos(x)^2 + sin(x)^2) is e x. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// Nothing simplifies the integrand, and no rule reads an exponent whose value is a + /// constant while it is written with x: it was declined. The simplifier's + /// , at the question asked, where a sum holds + /// both squares of one argument with the same coefficient. + /// + internal static Entity? SolveByFoldingAPythagoreanPair(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked || !expr.Nodes.Any(node => node is Sumf or Minusf && node.ContainsNode(x) && HoldsAPythagoreanPair(node))) + return null; + var folded = AngouriMath.Core.Transformations.RewriteRuleSetExtensions.Rewrite(expr, AngouriMath.Core.Transformations.RewriteRules.PythagoreanIdentity).InnerSimplified; + if (folded == expr || folded == expr.InnerSimplified) + return null; + return Integration.ComputeAsTheSameQuestion(folded, x, integrateByParts); + + static bool HoldsAPythagoreanPair(Entity sum) + { + var squares = new List<(Entity Coefficient, Entity Argument, bool Sine)>(); + foreach (var term in Sumf.LinearChildren(sum)) + { + Entity coefficient = Number.Integer.One; + (Entity Argument, bool Sine)? square = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(term)) + { + if (!underneath && square is null && factor is Powf(Sinf(var s), Number.Integer { EInteger: var two }) && two.Equals(EInteger.FromInt32(2))) + square = (s, true); + else if (!underneath && square is null && factor is Powf(Cosf(var c), Number.Integer { EInteger: var twice }) && twice.Equals(EInteger.FromInt32(2))) + square = (c, false); + else + coefficient = underneath ? coefficient / factor : coefficient * factor; + } + if (square is { } found) + { + if (squares.Any(other => other.Sine != found.Sine && other.Argument == found.Argument && VanishesIdentically(other.Coefficient - coefficient))) + return true; + squares.Add((coefficient, found.Argument, found.Sine)); + } + } + return false; + } + } + /// /// A whole power of a + b u, where u is the inverse sine or cosine of /// w = c + d x^2 with c^2 = 1, the inverse hyperbolic cosine of one, or the @@ -37277,6 +37668,66 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u) || node is Tanf(var argument) && argument == u || node is Cotanf(var cotangentArgument) && cotangentArgument == u); + /// + /// over , a polynomial in + /// of two terms or more, where a factor of it above the bar -- or the + /// sum of the polynomial parts of its terms, where every term has the same rest -- is a + /// constant multiple of the derivative: the constant times the rest. Null otherwise. + /// + private static Entity? WithAFactorProportionalToTheDerivative(Entity expr, Entity derivative, Entity.Variable x) + { + // Small ones only, and read once a factor is offered. + if (derivative is not (Sumf or Minusf) || derivative.Complexity > 40) + return null; + Dictionary? wanted = null; + Entity? Ratio(Entity polynomial) + { + if (wanted is null && (!TreeAnalyzer.TryGetPolynomial(derivative, x, out wanted) || wanted.Count < 2)) + { + wanted = new Dictionary(); + return null; + } + if (wanted.Count < 2 || !polynomial.ContainsNode(x) || polynomial.Complexity > 40 || !TreeAnalyzer.TryGetPolynomial(polynomial, x, out var read) + || read.Count != wanted.Count || !read.Keys.All(wanted.ContainsKey)) + return null; + var leading = wanted.Keys.Max()!; + var ratio = Functions.PartialFractions.Bare((read[leading] / wanted[leading]).Simplify()); + return !ratio.ContainsNode(x) && wanted.All(pair => VanishesIdentically(read[pair.Key] - ratio * pair.Value)) ? ratio : null; + } + // The terms of a sum with one rest each, their polynomial parts added. + if (expr is Sumf or Minusf) + { + Entity? common = null; + Entity polynomialParts = Number.Integer.Zero; + foreach (var term in Sumf.LinearChildren(expr)) + { + Entity polynomialPart = Number.Integer.One, rest = Number.Integer.One; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(term)) + if (!factor.ContainsNode(x) || !underneath && TreeAnalyzer.TryGetPolynomial(factor, x, out var monomials) && monomials.Keys.All(key => key.Sign >= 0)) + polynomialPart = underneath ? polynomialPart / factor : polynomialPart * factor; + else + rest = underneath ? rest / factor : rest * factor; + if (!rest.ContainsNode(x) || common is not null && rest != common) + return null; + common = rest; + polynomialParts += polynomialPart; + } + return common is not null && Ratio(polynomialParts) is { } k ? k * common : null; + } + Entity others = Number.Integer.One; + Entity? constant = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (constant is null && !underneath && factor is Sumf or Minusf && Ratio(factor) is { } k) + { + constant = k; + continue; + } + others = underneath ? others / factor : others * factor; + } + return constant is null ? null : constant * others; + } + /// Whether a linear renaming is being asked as the same question on this thread. [System.ThreadStatic] private static bool renamingLinearly; @@ -37466,6 +37917,14 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u) integrandInU = SimplifiedWithoutTheImaginaryUnit(cancelled, expr); } } + // A polynomial factor that is a constant multiple of du/dx and not written as + // it, divided out exactly: `(b/(2 c) + x) tan(a + b x + c x^2)` over `b + 2 c x` + // is `tan(u)/(2 c)`, which the simplification of the quotient did not find + // beside a tangent or a square, and Rubi's `b tan(Q)/(2 c) + x tan(Q)` was declined. + // https://github.com/asc-community/AngouriMath/issues/233 + if (integrandInU.ContainsNode(x) && WithAFactorProportionalToTheDerivative(source, duDx, x) is { } divided + && InTermsOf(divided, u, uSub, x) is var dividedInU && !dividedInU.ContainsNode(x)) + integrandInU = SimplifiedWithoutTheImaginaryUnit(dividedInU, expr); // Powers of one base on the two sides of the bar: `e^(c - b^2 x^2)` over the // `e^(-b^2 x^2)` of du/dx is `e^c`, which the one-level simplification leaves as // written, and `e^(c - b^2 x^2) erf(b x)` was refused under `u = erf(b x)` for diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index b6ce0aee2..0939ac47a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A sum free of the variable that comes to a number multiplied out, as that number, // before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`. if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer; + // And a squared sine beside the squared cosine of its argument, the one they add up to: + // `e^(cos(x)^2 + sin(x)^2)` was declined, written with an x its value does not hold. + if ((answer = IndefiniteIntegralSolver.SolveByFoldingAPythagoreanPair(expr, x, integrateByParts)) is { }) return answer; // An arctangent over a power of d (1 +- i c x), under x = tan(u)/c, where 1 + i tan(u) is // e^(i u)/cos(u): the rules below ran out of time on `(a + b atan(c x))^2/(d + i c d x)^2`. if ((answer = IndefiniteIntegralSolver.SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(expr, x, integrateByParts)) is { }) return answer; @@ -968,8 +971,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if ((answer = IndefiniteIntegralSolver.SolveASumOfPowersOfAnInverseOrErrorFunctionByPartsDownThePowers(expr, x, integrateByParts)) is { }) return answer; // A product of powers times a sum that is the derivative of the product with some // of the powers raised by one -- `e^x x^2 ln(x)^2 (3 + (3 + x) ln(x))` -- read off - // the sum, for symbolic exponents too. Before the split, which loses it. - if ((answer = IndefiniteIntegralSolver.SolveAsTheDerivativeOfAProductOfPowers(expr, x)) is { }) return answer; + // the sum, for symbolic exponents too. Before the split, which loses it. A sine, cosine or + // exponential in the bracket may be raised from nothing, and what is left beside the + // derivative may be a constant times it alone, asked: `sinh(Q)/x^2 - b cosh(Q)/x`. + if ((answer = IndefiniteIntegralSolver.SolveAsTheDerivativeOfAProductOfPowers(expr, x, integrateByParts)) is { }) return answer; // And a power of a binomial in x^n beside a power of x, with a symbol in the exponents, // where Chebyshev's third case is a whole number at most 0: by Rubi's reduction, which // the rule above does not read for a power on its own or past the first step. @@ -1016,6 +1021,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // search past the budget for want of the radical algebra the substitution would // have needed. if ((answer = IndefiniteIntegralSolver.SolveALogarithmicDerivativeOfARadicalSum(expr, x)) is { }) return answer; + // A negative exponential in a term of a sum below the bar, multiplied out above and below: + // `(cos(x) + sin(x))/(e^(-x) + sin(x))` is the logarithmic derivative of `1 + e^x sin(x)`. + if ((answer = IndefiniteIntegralSolver.SolveByClearingANegativeExponentialBelowTheBar(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveLogarithmic(expr, x, integrateByParts)) is { }) return answer; // Two or more square roots of polynomials written as one, before the substitution // looks for the one subtree it replaces: `x/(sqrt(1 + x^2) sqrt(1 - x^2))` is diff --git a/Sources/Tests/UnitTests/Calculus/AnExactDerivativeWithAFunctionRaisedFromNothingIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExactDerivativeWithAFunctionRaisedFromNothingIntegralTest.cs new file mode 100644 index 000000000..2ba7bce02 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnExactDerivativeWithAFunctionRaisedFromNothingIntegralTest.cs @@ -0,0 +1,87 @@ +// +// 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 +{ + /// + /// Integrands that are the derivative of a product of their powers with a function from + /// inside their bracket in it besides -- F^(c (a + b x)) (f x)^m sin(d + e x) -- or that + /// and a constant times a function of the bracket, with a power whose base and exponent both + /// hold x kept as an exponential is, and three rewritings: a Pythagorean pair in a sum written + /// as one, a negative exponential below the bar multiplied out, and a polynomial factor that is + /// a constant multiple of a substitution's derivative divided out exactly. Rubi's 4.7.6, 4.7.7, + /// 6.1.4, 6.2.4 and 6.7.1; each was declined, or ran out of time splitting the sum into terms + /// that are not elementary alone. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AnExactDerivativeWithAFunctionRaisedFromNothingIntegralTest + { + [Theory] + // A sine raised from nothing beside a symbolic power and an exponential of a constant base. + [InlineData("f*g^(c*(a+b*x))*(f*x)^(-2+m)*(k*x*cos(d+k*x)+(-1+m+b*c*x*ln(g))*sin(d+k*x))")] + [InlineData("f*g^(c*(a+b*x))*(f*x)^m*(k*x*cos(d+k*x)+(1+m+b*c*x*ln(g))*sin(d+k*x))")] + [InlineData("g^(c*(a+b*x))*(f*x)^m*(k*x*cos(d+k*x)+(m+b*c*x*ln(g))*sin(d+k*x))/x")] + // A sum whose cosine is in one term only, with x/sqrt(R) taken out of all three. + [InlineData("x^4/(b*sqrt(x^3+3*sin(a+b*x)))+x^2*cos(a+b*x)/sqrt(x^3+3*sin(a+b*x))+4/3*x*sqrt(x^3+3*sin(a+b*x))/b")] + // The derivative of -sinh(Q)/x beside 2 c cosh(Q), whose integral is the error functions'. + [InlineData("-b*cosh(a+b*x+c*x^2)/x+sinh(a+b*x+c*x^2)/x^2")] + [InlineData("-b*cosh(a+b*x-c*x^2)/x+sinh(a+b*x-c*x^2)/x^2")] + [InlineData("cosh(a+b*x+c*x^2)/x^2-b*sinh(a+b*x+c*x^2)/x")] + [InlineData("cosh(a+b*x-c*x^2)/x^2-b*sinh(a+b*x-c*x^2)/x")] + // A power of a variable base to a variable exponent, kept. + [InlineData("cosh(x)^x*(ln(cosh(x))+x*tanh(x))")] + // The two rewritings. + [InlineData("e^(cos(x)^2+sin(x)^2)")] + [InlineData("(cos(x)+sin(x))/(1/e^x+sin(x))")] + // A polynomial factor, or the polynomial parts of a sum, a constant multiple of the + // derivative of the substitution's quadratic. + [InlineData("1/2*b*tan(a+b*x+c*x^2)/c+x*tan(a+b*x+c*x^2)")] + [InlineData("(b/(2*c)+x)*sin(a+b*x+c*x^2)^2")] + [InlineData("(b/(2*c)+x)*sec(a+b*x+c*x^2)^2")] + public void DifferentiatesBack(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.DoesNotContain("NaN", text); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3) + .Substitute("f", 1.1).Substitute("g", 2.3).Substitute("k", 0.9).Substitute("m", 1.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 1.9, 2.9, 3.4 }) + { + 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}"); + } + + /// + /// The candidate itself, closed: a sine raised beside the powers gives the one product, with + /// no integral asked of anything. + /// + [Fact] + public void TheSineIsRaisedIntoOneProduct() + { + var text = "g^(c*(a+b*x))*(f*x)^m*(k*x*cos(d+k*x)+(m+b*c*x*ln(g))*sin(d+k*x))/x".ToEntity().Integrate("x").Stringize(); + Assert.Contains("sin(d + k * x)", text); + Assert.DoesNotContain("cos(", text); + } + } +}