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); + } + } +}