Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,20 @@ interval without a case for each. Only where nothing else answers the integrand
| `"acoth(x)^2/(1-x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,304 characters, with `(ln((x + 1)/(x - 1)) - ln((1 + x)/(1 - x)))/2` for `K` |
| `"acoth(c*x)^2/(1-c^2*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,681 characters |

### A power of a sum of exponentials that is one exponential is written as it

**Answers where there were none, and a shorter one.** The parser writes `cosh(u) + sinh(u)` as
`(e^u + e^(-u))/2 + (e^u - e^(-u))/2`, and nothing collected it. Rubi's 6.7.1 `F^(a + b x) (cosh(c + d x) + sinh(c + d x))^n`
and its twin with the difference were declined. A power of such a sum is now written as the one exponential it is,
`c^n e^(p n u)` for a sum that is `c e^(p u)`, which holds for a real `u` since `e^(p u)` is positive
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"F^(a+b*x)*(cosh(c+d*x)+sinh(c+d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | 160 characters |
| `"F^(a+b*x)*(cosh(c+d*x)-sinh(c+d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | 165 characters |
| `"(cosh(x)+sinh(x))^3".ToEntity().Integrate("x")` | 787 characters | `e^(3 x)/3`, 19 characters |

### A function of the hyperbolic tangent alone is integrated under `u = tanh`

**Answers where there were none.** `tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4)` was declined, and so were 35 more of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41968,6 +41968,60 @@ Entity Times(Entity term)
/// <summary>Set while <see cref="SolveByClearingANegativeExponentialBelowTheBar"/> asks its question.</summary>
[System.ThreadStatic] private static bool clearingANegativeExponential;

/// <summary>
/// A power of a sum of exponentials of x that is one exponential, written as it: Rubi's
/// <c>F^(a + b x) (cosh(c + d x) + sinh(c + d x))^n</c> is <c>F^(a + b x) e^(n (c + d x))</c>.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </summary>
/// <remarks>
/// The parser writes the hyperbolic functions as exponentials, and the sum is
/// <c>(e^u + e^(-u))/2 + (e^u - e^(-u))/2</c> as written, which nothing collects: beside
/// <c>F^(a + b x)</c> it was declined. With each exponential a whole power of the first,
/// <c>w</c>, the base is a Laurent polynomial in <c>w</c>, and where one coefficient is left it
/// is <c>c w^p</c>; <c>(c w^p)^n</c> is <c>c^n w^(p n)</c> for a positive <c>w</c>, which <c>e</c> to a
/// real power is. At the question asked.
/// </remarks>
internal static Entity? SolveByFoldingASumOfExponentialsIntoOne(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
static bool IsAnExponential(Entity node, Entity.Variable x) => node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x);
var folded = expr.Replace(node =>
{
if (node is not Powf(Sumf or Minusf, var power) raised || power.ContainsNode(x) || power == Number.Integer.One)
return node;
var sum = raised.Base;
var exponentials = sum.Nodes.Where(inner => IsAnExponential(inner, x)).Distinct().ToList();
if (exponentials.Count < 2
|| sum.Replace(inner => IsAnExponential(inner, x) ? Number.Integer.One : inner).ContainsNode(x))
return node;
// Each a whole power of the first, e^(m + k x).
var first = ((Powf)exponentials[0]).Exponent;
var powers = new Dictionary<Entity, int>();
foreach (var exponential in exponentials)
if (Functions.PartialFractions.Bare((((Powf)exponential).Exponent / first).Simplify()) is Number.Integer { EInteger: var whole } && whole.CanFitInInt32())
powers[exponential] = whole.ToInt32Unchecked();
else
return node;
var w = Variable.CreateUnique(expr, "w_fold");
var inW = sum.Replace(inner => powers.TryGetValue(inner, out var k) ? MathS.Pow(w, Number.Integer.Create(k)) : inner);
// Expanded, `(w + 1/w)/2 + (w - 1/w)/2` is `w provided not w = 0`, and w is never 0.
if (!TreeAnalyzer.TryGetPolynomial(Functions.PartialFractions.Bare(inW.Expand()), w, out var read))
return node;
var left = read.Where(pair => !Functions.PartialFractions.IsZeroAsAValue(pair.Value)).ToList();
if (left.Count != 1 || !left[0].Key.CanFitInInt32())
return node;
var (degree, coefficient) = (left[0].Key.ToInt32Unchecked(), Functions.PartialFractions.Bare(left[0].Value.InnerSimplified));
var exponentialOfThePower = MathS.Pow(MathS.e, (power * degree * first).InnerSimplified);
return coefficient == Number.Integer.One ? exponentialOfThePower : MathS.Pow(coefficient, power) * exponentialOfThePower;
});
if (folded == expr)
return null;
// Inner-simplified, which takes the `ln(e)` the rule for `F^u` writes for the base to 1.
var answer = Integration.ComputeAsTheSameQuestion(folded.InnerSimplified, x, integrateByParts)?.InnerSimplified;
return answer is not null && Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
}

/// <summary>
/// A squared sine and a squared cosine of one argument in a sum, written as the one they
/// add up to: Rubi's <c>e^(cos(x)^2 + sin(x)^2)</c> is <c>e x</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,8 @@ internal static bool ASumOverRootsWouldAnswer
// 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;
// And a power of a sum of exponentials that is one exponential, the hyperbolic `cosh(u) + sinh(u)`.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingASumOfExponentialsIntoOne(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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// 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
{
/// <summary>
/// A power of a sum of exponentials that is one exponential, written as it: the parser writes
/// <c>cosh(u) + sinh(u)</c> as <c>(e^u + e^(-u))/2 + (e^u - e^(-u))/2</c>, which nothing collected, and
/// Rubi's 6.7.1 <c>F^(a + b x) (cosh(c + d x) + sinh(c + d x))^n</c> was declined.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfASumOfExponentialsThatIsOneIntegralTest
{
[Theory]
[InlineData("F^(a + b*x)*(cosh(c + d*x) + sinh(c + d*x))^n", 400)]
[InlineData("F^(a + b*x)*(cosh(c + d*x) - sinh(c + d*x))^n", 400)]
[InlineData("(cosh(x) + sinh(x))^3", 40)]
[InlineData("x*(cosh(x) - sinh(x))^(5/2)", 120)]
public void IsOneExponential(string integrand, int characters)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < characters, $"the antiderivative of {integrand} is {text.Length} characters: {text}");
Entity Pinned(Entity e) => e.Substitute("F", 1.7).Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4)
.Substitute("d", 0.9).Substitute("n", 0.6);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.7, -0.6, 0.4, 1.3, 2.2 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
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}");
}
}
}
}
Loading