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