Skip to content
Merged
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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,21 @@ taken apart into pieces each closed the same way
| `"(2 + x)/((2 + 4*x - 3*x^2)*(1 + 3*x + 2*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative |
| `"1/((x^2 + 1)*sqrt(x^2 + x + 1))".ToEntity().Integrate("x")` | `integral(...)` | a logarithm and an arctangent |

### A power of a multiple of a quadratic's derivative beside a power of the quadratic is a binomial

**Improvement, not silent.** `(b d + 2 c d x)^m (a + b x + c x^2)^p`, with a power that is not whole,
was declined or ran out of time. Under `t = b d + 2 c d x` the quadratic is
`(t^2/d^2 - (b^2 - 4 a c))/(4 c)`, so the integrand is a binomial in `t`. That binomial is asked with
its constant term named by a symbol of its own, since with `a - b^2/(4 c)` written in it the rules for
a binomial ran out of time too. Rubi's 1.2.1.2
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a+b*x+c*x^2)^(3/2)/(b*d+2*c*d*x)^3".Integrate("x")` | `integral(...)` | the antiderivative |
| `"(a+b*x+c*x^2)^(1/2)/(b*d+2*c*d*x)^7".Integrate("x")` | `integral(...)` | the antiderivative |
| `"(b*d+2*c*d*x)^(5/2)/(a+b*x+c*x^2)^3".Integrate("x")` | `integral(...)` | the antiderivative |

### A polynomial over a power of a quadratic beside the root of another is integrated

`P/(A^k sqrt(B))`, `A` and `B` two different quadratics and `k` at least two, was left
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14072,6 +14072,88 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
return assumed == Entity.Boolean.True ? answer : answer.Provided(assumed);
}

/// <summary>
/// A power of a linear that is a constant multiple of a quadratic's derivative, beside a
/// power of the quadratic, under the linear as the variable: with <c>t = λ (b + 2 c x)</c>
/// the quadratic <c>a + b x + c x^2</c> is <c>(t^2/λ^2 - Δ)/(4 c)</c>, with
/// <c>Δ = b^2 - 4 a c</c>, so <c>t^m Q^p</c> is a binomial in <c>t</c>. Rubi's 1.2.1.2,
/// <c>(b d + 2 c d x)^m (a + b x + c x^2)^p</c>, which it substitutes the same way.
/// </summary>
/// <remarks>
/// The substitution is affine, so the integrand in <c>t</c> is the integrand exactly. The
/// quadratic is written in <c>t</c> from its coefficients rather than by substituting:
/// substituted, it keeps a linear term that is zero as a value and not as written, and the
/// rules for a binomial read <c>α + β t^2</c>. Only where a power is not whole: a rational
/// function is the partial fractions'.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByTheDerivativeOfAQuadraticAsTheVariable(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAskedOrOneBelow)
return null;
Entity constant = Number.Integer.One;
(Entity Base, Entity Exponent)? linear = null, quadratic = null;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
{
if (!factor.ContainsNode(x))
{
constant = underneath ? constant / factor : constant * factor;
continue;
}
var (@base, exponent) = factor is Powf(var b, var e) && !e.ContainsNode(x) ? (b, e) : (factor, (Entity)Number.Integer.One);
if (underneath)
exponent = (-exponent).InnerSimplified;
if (!TreeAnalyzer.TryGetPolynomial(@base, x, out var read) || read.Count == 0 || read.Keys.Any(k => k.Sign < 0)
|| read.Values.Any(coefficient => coefficient.ContainsNode(x)))
return null;
var degree = read.Keys.Max()!;
if (degree.Equals(EInteger.One) && linear is null)
linear = (@base, exponent);
else if (degree.Equals(EInteger.FromInt32(2)) && quadratic is null)
quadratic = (@base, exponent);
else
return null;
}
if (linear is not var (l, m) || quadratic is not var (q, p) || m is Number.Integer && p is Number.Integer)
return null;
if (!TreeAnalyzer.TryGetPolyLinear(l, x, out var l1, out var l0)
|| !TreeAnalyzer.TryGetPolyQuadratic(q, x, out var c2, out var c1, out var c0)
|| VanishesIdentically(c2) || VanishesIdentically(l1)
// With no linear term the integrand is a binomial in x already, and taken as one in
// t it would be this question again with t scaled.
|| VanishesIdentically(c1)
// The linear is a multiple of 2 c x + b where its root is the vertex's.
|| !VanishesIdentically(2 * c2 * l0 - c1 * l1))
return null;
var lambda = Reduced(l1 / (2 * c2));
// Q = (t^2/λ^2 - Δ)/(4 c) = α + β t^2, each constant named by a symbol of its own where
// it is a compound of the coefficients, and written back into the answer: the rules for
// a binomial answer `t^(-7) sqrt(c t^2 + k)` in milliseconds, and with
// `a - b^2/(4 c)` for `k` they ran out of time.
var alpha = Reduced(-(c1 * c1 - 4 * c2 * c0) / (4 * c2));
var beta = Reduced(1 / (4 * c2 * lambda * lambda));
var t = Variable.CreateUnique(expr, "t_deriv");
var named = new List<(Variable Name, Entity Value)>();
Entity Named(Entity value)
{
if (value is Number or Variable || value is Mulf(Number, Variable) || value is Powf(Variable, Number))
return value;
var name = Variable.CreateUnique(expr + t + named.Aggregate((Entity)Number.Integer.Zero, (sum, pair) => sum + pair.Name), "k_named");
named.Add((name, value));
return name;
}
var inT = Reduced(constant / l1) * MathS.Pow(t, m) * MathS.Pow(Named(alpha) + Named(beta) * MathS.Sqr(t), p);
if (Integration.ComputeAsAQuestionOfItsOwn(inT, t, integrateByParts) is not { } answer
|| answer.Nodes.Any(node => node == MathS.NaN))
return null;
foreach (var (name, value) in named)
answer = answer.Substitute(name, value);
return answer.Substitute(t, l);

static Entity Reduced(Entity constant)
=> constant.Vars.Any() ? Functions.PartialFractions.InLowestTermsOverTheSymbols(constant) : constant.InnerSimplified;
}

/// <summary>
/// A whole power of a product in which the variable stands beside a constant, written
/// as the product of the powers: <c>(c x)^2</c> is <c>c^2 x^2</c>, exact for a whole
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveAReciprocalOfAnInverseTrigonometricFunction(expr, x, integrateByParts)) is { }) return answer;
// A fractional power of a perfect square is the power of the modulus, sgn(P) P^(2r).
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquare(expr, x, integrateByParts)) is { }) return answer;
// A power of a multiple of a quadratic's derivative beside a power of the quadratic is
// a binomial in the derivative: `(b d + 2 c d x)^m (a + b x + c x^2)^p`.
if ((answer = IndefiniteIntegralSolver.SolveByTheDerivativeOfAQuadraticAsTheVariable(expr, x, integrateByParts)) is { }) return answer;
// x^(n - 1) g(x^n) with a symbolic n is g(u)/n under u = x^n.
if ((answer = IndefiniteIntegralSolver.SolveByAPowerOfTheVariableTimesAFunctionOfItsPower(expr, x, integrateByParts)) is { }) return answer;
// A whole power of a product of a constant and the variable, as the product of
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// 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 multiple of a quadratic's derivative beside a power of the quadratic, Rubi's
/// 1.2.1.2 <c>(b d + 2 c d x)^m (a + b x + c x^2)^p</c>: under <c>t = b d + 2 c d x</c> the
/// quadratic is <c>(t^2/d^2 - (b^2 - 4 a c))/(4 c)</c>, and the integrand a binomial in <c>t</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Each was declined, or ran past the corpus's budget; the binomial with a compound constant
/// term ran out of time too, and is asked with the constant named by a symbol of its own.
/// Checked by differentiating back with the symbols pinned, at points on both sides of the
/// derivative's root where the integrand is real.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class DerivativeOfAQuadraticAsTheVariableTest
{
[Theory]
[InlineData("(a + b*x + c*x^2)^(3/2)/(b*d + 2*c*d*x)^3")]
[InlineData("(a + b*x + c*x^2)^(1/2)/(b*d + 2*c*d*x)^7")]
[InlineData("(a + b*x + c*x^2)^(5/2)/(b*d + 2*c*d*x)^5")]
[InlineData("(b*d + 2*c*d*x)^(5/2)/(a + b*x + c*x^2)^3")]
public void IsABinomialInTheDerivative(string integrand)
{
var integral = integrand.ToEntity().Integrate("x").Substitute("C", 0);
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 2.3).Substitute("b", 1.1).Substitute("c", 0.7).Substitute("d", 1.9);
var derivative = Pin(integral).Differentiate("x");
var original = Pin(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -3.1, -1.4, 0.3, 0.9, 1.7 })
{
var want = original.Substitute("x", at).EvalNumerical();
if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12 * Math.Max(1, Math.Abs((double)want.RealPart)))
continue;
var got = derivative.Substitute("x", at).EvalNumerical();
compared++;
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
Assert.True(difference < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.True(compared >= 3, $"only {compared} points where {integrand} is real");
}
}
}
Loading