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 @@ -4125,6 +4125,21 @@ a binomial ran out of time too. Rubi's 1.2.1.2
| `"(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 polynomial beside a power of a quadratic is divided without the quadratic's condition

**Answers where there were none.** `x^2/((1 + x^2) (a + b x + c x^2)^(3/2))` was left unevaluated, and so was Rubi's
4.3.9 `tan^2/(a + b tan + c tan^2)^(3/2)`, which the tangent writes so, while the tangent and its cube over the same
were answered. The division of the numerator leaves `1/Q^(3/2)` and `-1/((1 + x^2) Q^(3/2))`, both answered, but wrote
on the second the condition that `Q` is not zero, which nothing read. That condition holds wherever the integrand is
defined, and where every other rule has declined the second part is asked without it; a condition on the symbols
alone is kept ([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"tan(d+f*x)^2/(a+b*tan(d+f*x)+c*tan(d+f*x)^2)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 3,162 characters |
| `"x^2/((1+x^2)*(a+b*x+c*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 2,966 characters |
| `"x^2/((2+x^2)*(a+b*x+c*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 3,416 characters |

### 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 @@ -27418,6 +27418,67 @@ private static bool HoldsARepeatedSymbolicFactor(Entity denominator, Entity.Vari
=> Mulf.LinearChildren(denominator).Any(factor => factor is Powf(var @base, Number.Integer { EInteger.Sign: > 0 } power)
&& power != Number.Integer.One && @base.ContainsNode(x) && @base.Vars.Any(v => v != x));

/// <summary>
/// A polynomial over a polynomial times a power of something else, divided as
/// <see cref="SolveByPartialFractions"/> divides it, with the part over the denominator
/// asked without the condition the division writes on it, that the other factor is not
/// zero -- which holds wherever the integrand is defined.
/// </summary>
/// <remarks>
/// The division of <c>x^2</c> by <c>(1 + x^2) Q^(3/2)</c> leaves
/// <c>(-1 provided Q != 0)/((1 + x^2) Q^(3/2))</c>, which nothing reads, where the same
/// without the condition is answered, and so is the quotient <c>1/Q^(3/2)</c>: Rubi's 4.3.9
/// <c>tan^2/(a + b tan + c tan^2)^(3/2)</c>, under the tangent, was declined while
/// <c>tan</c> and <c>tan^3</c> over the same were answered. Last, where everything else has
/// declined, and at the question asked or the one below it, where the tangent's
/// substitution asks it: asked inside the division, the parts answered first and four
/// solved rows of 4.3.9 and 4.4.9 came back longer. Only a condition that a factor of the
/// denominator with x in it is not zero is dropped; one on the symbols alone keeps it out.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </remarks>
internal static Entity? SolveByDividingWithoutTheConditionsOfTheDenominator(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAskedOrOneBelow
|| !TryReadAsQuotient(expr, out var numerator, out var denominator)
|| !TreeAnalyzer.TryGetPolynomial(numerator, x, out _)
|| IsProperByDegree(numerator, denominator, x)
|| TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is not var (quotient, properPart)
|| quotient.Evaled == Entity.Number.Integer.Create(0)
|| WithoutTheConditionsOfItsDenominator(properPart, denominator, x) is not { } unconditioned
|| Integration.ComputeIndefiniteIntegral(quotient, x, integrateByParts) is not { } wholePart
|| Integration.ComputeIndefiniteIntegral(unconditioned, x, integrateByParts) is not { } fractionPart)
return null;
return wholePart + fractionPart;
}

/// <summary>
/// <paramref name="part"/> with every condition in it that a factor of
/// <paramref name="denominator"/> holding <paramref name="x"/>, or the base of a positive
/// power among them, is not zero dropped, as it
/// holds wherever the integrand is defined; null where it has no condition, or one of
/// another kind.
/// </summary>
private static Entity? WithoutTheConditionsOfItsDenominator(Entity part, Entity denominator, Entity.Variable x)
{
var dropped = false;
var other = false;
var written = part.Replace(node =>
{
if (node is not Providedf(var inner, var condition))
return node;
if (condition is Notf(Equalsf(var factor, var zero)) && zero.Evaled is Number.Complex { IsZero: true }
&& factor.ContainsNode(x) && Mulf.LinearChildren(denominator).Any(written =>
written == factor || written is Powf(var @base, Number.Real { IsPositive: true }) && @base == factor))
{
dropped = true;
return inner;
}
other = true;
return node;
});
return dropped && !other ? written.InnerSimplified : null;
}

/// <summary>
/// Whether both read as polynomials in <paramref name="x"/> with the numerator's
/// degree below the denominator's; false where either does not read.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A hyperbolic integrand that is not rational in its exponential, asked as a trigonometric
// one: last, since the question in s can take long where a rule above answers in x at once.
if ((answer = IndefiniteIntegralSolver.SolveAHyperbolicIntegrandAsATrigonometricOne(expr, x, integrateByParts, rationalToo: false)) is { }) return answer;
// A polynomial over a polynomial beside a power, divided, the part over the denominator
// asked without the division's condition that the power is not zero: last, where nothing
// has answered it with the condition, `x^2/((1 + x^2) (a + b x + c x^2)^(3/2))`.
if ((answer = IndefiniteIntegralSolver.SolveByDividingWithoutTheConditionsOfTheDenominator(expr, x, integrateByParts)) is { }) return answer;
return null;
}
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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 polynomial over a polynomial beside a power of a quadratic, divided, and the part over
/// the denominator asked without the condition the division writes on it, that the quadratic
/// is not zero: <c>x^2/((1 + x^2) Q^(3/2))</c> is <c>1/Q^(3/2) - 1/((1 + x^2) Q^(3/2))</c>, and
/// with the condition nothing read the second. Rubi's 4.3.9
/// <c>tan^2/(a + b tan + c tan^2)^(3/2)</c>, under the tangent, was declined while the tangent
/// and its cube over the same were answered.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class DividingWithoutTheConditionsOfTheDenominatorIntegralTest
{
[Theory]
[InlineData("tan(d + f*x)^2/(a + b*tan(d + f*x) + c*tan(d + f*x)^2)^(3/2)")]
[InlineData("x^2/((1 + x^2)*(a + b*x + c*x^2)^(3/2))")]
[InlineData("x^2/((2 + x^2)*(a + b*x + c*x^2)^(3/2))")]
public void IsAnswered(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 0.7).Substitute("b", 1.1).Substitute("c", 1.4).Substitute("d", 0.3).Substitute("f", 0.9);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -2.9, -1.7, -0.35, 0.25, 0.8, 1.4, 2.2 })
{
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}");
}
}
}
Loading