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
20 changes: 20 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,26 @@ quadratic with numbers in it, although beside the root each piece is a shape the
| `"(2 + 5*x + x^2)*sqrt(3 + 2*x + 5*x^2)/(1 + 4*x - 7*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 5,242 characters |
| `"(a + b*x + c*x^2)^(1/2)/(d + e*x + f*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 50,758 characters, six symbols |

### A power of a perfect square with a symbolic leading coefficient is a power of its root

**Answers where there were none.** `sqrt(c d^2 + 2 c d k x + c k^2 x^2)` was declined: the square root of
`c (d + k x)^2` was taken as `sqrt(c) |d + k x|` only for a leading coefficient whose sign was known. It
holds for every nonzero one, since `(d + k x)^2` is positive, and is taken so now. Beside a power of the
square's own linear, that linear is taken as the root, so the two powers gather into one. The reciprocal
root, which 2.5.0 answered in 411 characters and the unreleased master declined, is 61 characters now
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sqrt(c*d^2 + 2*c*d*k*x + c*k^2*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 62 characters |
| `"(c*d^2 + 2*c*d*k*x + c*k^2*x^2)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 62 characters |
| `"1/sqrt(c*d^2 + 2*c*d*k*x + c*k^2*x^2)".ToEntity().Integrate("x")` | 411 characters | 61 characters |
| `"1/(c*d^2 + 2*c*d*k*x + c*k^2*x^2)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 74 characters |
| `"(d + k*x)^(-1 - 2*p)*(c*d^2 + 2*c*d*k*x + c*k^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)` | 103 characters |
| `"1/((d + k*x)*sqrt(b^2/(4*c) + b*x + c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,277 characters |
| `"sqrt(a + a*sech(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 274 characters |
| `"asinh(sqrt(b*x^2 - 1))^3/sqrt(b*x^2 - 1)".ToEntity().Integrate("x")` | `integral(...)` | 166 characters |

### A square of a symbolic quadratic beside two linears is answered within the budget

**Answers where there were none in time.** `1/((d + k x)(f + g x)(a + b x + c x^2)^2)` was declined by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21417,12 +21417,17 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
// with the answer. The other two coefficients are real or symbolic, not a
// number off the line, so that `(x + h)^2` is not negative.
// A whole power needs no sign of anything: (a (w + h)^2)^n is a^n (w + h)^(2n).
// And one of no known sign: for a real w + h, (w + h)^2 is positive, so
// Log(a (w + h)^2) is Log(a) + 2 ln|w + h| for every nonzero a, and
// (a (w + h)^2)^r is a^r |w + h|^(2r) -- `sqrt(c (d + k x)^2)` is
// `sqrt(c) |d + k x|` for a negative c as for a positive one.
if (!whole && a.Evaled is not Number.Real { IsZero: false })
{
if (!IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed)
&& !IsPositiveForARealParameter((-a).InnerSimplified, x, out leadingAssumed, assumed))
if (a.Evaled is Number.Complex and not Number.Real)
return node;
assumed = leadingAssumed;
if (IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed)
|| IsPositiveForARealParameter((-a).InnerSimplified, x, out leadingAssumed, assumed))
assumed = leadingAssumed;
}
var leading = a;
if (b.Evaled is Number.Complex and not Number.Real || c.Evaled is Number.Complex and not Number.Real
Expand Down Expand Up @@ -22058,6 +22063,21 @@ static Entity Exponent(Entity exponent)
if (found is not var (square, exponent, root, leading, squareIsBelow)
|| above.Nodes.Concat(below.Nodes).Count(node => node == square) != 1)
return null;
// A linear of the integrand that is a multiple of the root, `d + k x` beside
// `(c d^2 + 2 c d k x + c k^2 x^2)^p`, is the root itself, with the multiple's square
// taken out of the leading coefficient: its power and the square's then gather into
// one power of one linear, where `(x + d/k)^(2p) (d + k x)^(-1 - 2p)` were two.
if (root is Sumf && TreeAnalyzer.TryGetPolyLinear(root, x, out _, out var rootShift))
foreach (var factor in Mulf.LinearChildren(above).Concat(Mulf.LinearChildren(below)))
{
var linear = factor is Powf(var @base, var power) && !power.ContainsNode(x) ? @base : factor;
if (linear == square || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out var intercept)
|| slope.Evaled is Number.Complex { IsZero: true } || !VanishesIdentically(intercept - slope * rootShift))
continue;
root = linear;
leading = leading / (slope * slope);
break;
}
var twice = (2 * exponent).InnerSimplified;
// The root's power above the bar either way, with a negative exponent for the square
// below it: `x (x + h)^(-2p)` is read below the top where `x/(x + h)^(2p)` was not.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// 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 perfect square whose leading coefficient is a symbol of no known sign:
/// <c>(c (d + k x)^2)^r</c> is <c>(c k^2)^r |x + d/k|^(2r)</c> for a negative <c>c</c> as for a
/// positive one, since <c>(x + d/k)^2</c> is positive. Rubi's 1.2.1.2
/// <c>sqrt(c d^2 + 2 c d e x + c e^2 x^2)</c>, written here with <c>k</c> for <c>e</c>, which is
/// Euler's number.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APerfectSquareWithASymbolicLeadingCoefficientIntegralTest
{
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

// Pinned before it is differentiated: the answer carries `sgn(x + d/k)`, whose
// derivative is read where its argument is a number.
var pinned = integral.Substitute("C", 0);
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
pinned = pinned.Substitute(name, value);
original = original.Substitute(name, value);
}
var derivative = pinned.Differentiate("x");
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

[Theory]
[InlineData("sqrt(c*d^2+2*c*d*k*x+c*k^2*x^2)")]
[InlineData("(c*d^2+2*c*d*k*x+c*k^2*x^2)^(3/2)")]
[InlineData("1/sqrt(c*d^2+2*c*d*k*x+c*k^2*x^2)")]
[InlineData("1/(c*d^2+2*c*d*k*x+c*k^2*x^2)^(5/2)")]
[InlineData("(d+k*x)^(-1-2*p)*(c*d^2+2*c*d*k*x+c*k^2*x^2)^p")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("c", 3), ("d", 1.25), ("k", 2), ("p", 0.3));
DifferentiatesBack(integrand, ("c", -2), ("d", -0.5), ("k", 1.5), ("p", -1.7));
}
}
}
Loading