Skip to content

Commit 397aa97

Browse files
Name a symbolic quadratic's or binomial's coefficients beside a sine, and read a double root as a linear twice (#1880)
* Name a quadratic's compound coefficients beside a trigonometric, and read a double root as a linear twice Carried through the partial fractions over the roots, d e - c f came out expanded to the seventh degree in every coefficient: 11,876 characters for sin(a + b x)/(x ((d e - c f) x^2 + f)), now 980. And a quadratic whose discriminant is zero as a value was split over two equal roots, NaN everywhere. Fixes #1877 Fixes #1879 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Name a binomial's compound coefficients beside a trigonometric too sin(a + b/(c + d x)^(1/3))/(e + f x) reaches a cubic binomial in 1/t with d e - c f in it: 12,006 characters, now 1,406. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the named coefficients' answers in BREAKING-CHANGES.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent b3c34db commit 397aa97

3 files changed

Lines changed: 138 additions & 8 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,22 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take
396396
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
397397
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |
398398

399+
### A sine or a cosine over a symbolic quadratic or binomial is split over its roots with the coefficients named
400+
401+
**Answers where there were none.** A quadratic or a binomial with a compound coefficient below a sine or a cosine is
402+
split over its roots with each compound coefficient named, and the names are written back in the answer. Carried
403+
through the partial fractions, `d e - c f` was expanded to the seventh degree in every coefficient, and a quadratic
404+
with a double root was split over two equal roots, so its answer was NaN everywhere. A double root is now one linear
405+
twice ([#1877](https://github.com/asc-community/AngouriMath/issues/1877),
406+
[#1879](https://github.com/asc-community/AngouriMath/issues/1879)).
407+
408+
| Input | Was (2.5.0) | Now |
409+
|---|---|---|
410+
| `"sin(a + b*x)/(x*((d*e - c*f)*x^2 + f))".ToEntity().Integrate("x")` | `integral(...)` | 980 characters |
411+
| `"sin(a + b*x)/((c + d)*x^2 + (e - f)*x + 1)".ToEntity().Integrate("x")` | `integral(...)` | 921 characters |
412+
| `"sin(a + b*x)/((d*e - c*f)*x^3 + f)".ToEntity().Integrate("x")` | `integral(...)` | 1,416 characters |
413+
| `"sin(x)/(a*x^2 + 2*a*x + a)".ToEntity().Integrate("x")` | `integral(...)` | 246 characters |
414+
399415
### A trigonometric function of `a + b/x` beside a rational function is integrated in `1/x`
400416

401417
**Answers where there were none.** `sin(c + d/x)/(e + f x)` was declined: the substitution search reads a candidate's
@@ -404,17 +420,14 @@ partial fractions and the sine and cosine integrals answer, and `x = 1/u` is one
404420
root of a linear, the linear radical substitution gets there first
405421
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
406422

407-
The answers below a root are large because the sine over the linears that come out of the substitution carries
408-
expanded coefficients ([#1877](https://github.com/asc-community/AngouriMath/issues/1877)).
409-
410423
| Input | Was (2.5.0) | Now |
411424
|---|---|---|
412425
| `"sin(c + d/x)/(e + f*x)".ToEntity().Integrate("x")` | `integral(...)` | 196 characters |
413426
| `"(a + b*sin(c + d/x))/(e + f*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 470 characters |
414427
| `"(a + b*sin(c + d/x))^2/(e + f*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 2,516 characters |
415428
| `"(c*e + d*e*x)^(1/3)*sin(a + b/(c + d*x)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 1,453 characters |
416-
| `"sin(a + b/sqrt(c + d*x))/(e + f*x)".ToEntity().Integrate("x")` | `integral(...)` | 12,010 characters |
417-
| `"sin(a + b/sqrt(c + d*x))/(e + f*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 32,780 characters |
429+
| `"sin(a + b/sqrt(c + d*x))/(e + f*x)".ToEntity().Integrate("x")` | `integral(...)` | 1,114 characters |
430+
| `"sin(a + b/sqrt(c + d*x))/(e + f*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 2,288 characters |
418431

419432
### A power of the cosine beside a secant binomial is integrated in the cosine
420433

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8103,6 +8103,28 @@ private static (Entity Leading, Entity[] Roots, Entity Monic)? TheRootsOfABinomi
81038103
/// </summary>
81048104
private static Entity? OverAPowerOfALinear(Entity expr, Entity.Variable x, Entity argument)
81058105
{
8106+
var names = new List<(Variable Name, Entity Coefficient)>();
8107+
Entity Named(Entity coefficient)
8108+
{
8109+
if (coefficient is not (Sumf or Minusf) || !coefficient.Vars.Any())
8110+
return coefficient;
8111+
var name = Variable.CreateUnique(expr + names.Aggregate((Entity)Number.Integer.Zero, (all, pair) => all + pair.Name), "k_quad");
8112+
names.Add((name, coefficient));
8113+
return name;
8114+
}
8115+
Entity WrittenBack(Entity answer) => names.Aggregate(answer, (all, pair) => all.Substitute(pair.Name, pair.Coefficient));
8116+
// A binomial's two coefficients named the same way: its roots are distinct wherever
8117+
// its constant term is not zero, which the reading of its roots requires.
8118+
Entity WithItsCoefficientsNamed(Entity binomial)
8119+
{
8120+
if (!TreeAnalyzer.TryGetPolynomial(binomial, x, out var terms) || terms.Count != 2
8121+
|| terms.Values.Any(coefficient => coefficient.ContainsNode(x)))
8122+
return binomial;
8123+
if (!terms.TryGetValue(EInteger.Zero, out var free) || terms.Values.All(coefficient => coefficient is not (Sumf or Minusf)))
8124+
return binomial;
8125+
var top = terms.First(term => !term.Key.IsZero);
8126+
return Named(free) + Named(top.Value) * MathS.Pow(x, Number.Integer.Create(top.Key));
8127+
}
81068128
var (sine, cosine) = (MathS.Sin(argument), MathS.Cos(argument));
81078129
var (tangent, cotangent, secant, cosecant) = (MathS.Tan(argument), MathS.Cotan(argument), MathS.Sec(argument), MathS.Cosec(argument));
81088130
var s = Variable.CreateUnique(expr, "s");
@@ -8161,6 +8183,26 @@ void AddALinear(Entity linear, int power)
81618183
// cosine integrals of the two conjugate arguments add up to a real answer.
81628184
else if (TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var leading, out var middle, out var last) && !TreeAnalyzer.IsZero(leading))
81638185
{
8186+
// A double root is one linear twice: split over two roots that coincide, the
8187+
// partial fractions divided by their difference, and every value was NaN.
8188+
// https://github.com/asc-community/AngouriMath/issues/1879
8189+
if (Functions.PartialFractions.IsZeroAsAValue(middle * middle - 4 * leading * last))
8190+
{
8191+
var twice = (x + middle / (2 * leading)).InnerSimplified;
8192+
constant /= MathS.Pow(leading, -exponent);
8193+
AddALinear(twice, -2 * exponent);
8194+
written *= MathS.Pow(twice, -2 * exponent);
8195+
continue;
8196+
}
8197+
// A compound coefficient named for the split and written back in the answer:
8198+
// `d e - c f` carried through the partial fractions over the roots came out
8199+
// expanded to the seventh degree in every coefficient, 11,876 characters for
8200+
// `sin(a + b x)/(x ((d e - c f) x^2 + f))` against 575 for `k`. Not where the
8201+
// roots coincide, which a name hides, and which are a linear twice above.
8202+
// https://github.com/asc-community/AngouriMath/issues/1877
8203+
leading = Named(leading);
8204+
middle = Named(middle);
8205+
last = Named(last);
81648206
var discriminant = MathS.Sqrt(middle * middle - 4 * leading * last);
81658207
var first = ((-middle + discriminant) / (2 * leading)).InnerSimplified;
81668208
var second = ((-middle - discriminant) / (2 * leading)).InnerSimplified;
@@ -8170,7 +8212,7 @@ void AddALinear(Entity linear, int power)
81708212
written *= MathS.Pow(Monic(leading, middle, last, x), -exponent);
81718213
}
81728214
// And a binomial past the quadratic the linears of its roots: `sin(c + d x)/(a + b x^3)`.
8173-
else if (TheRootsOfABinomial(@base, x) is { } binomial)
8215+
else if (TheRootsOfABinomial(WithItsCoefficientsNamed(@base), x) is { } binomial)
81748216
{
81758217
constant /= MathS.Pow(binomial.Leading, -exponent);
81768218
foreach (var root in binomial.Roots)
@@ -8193,7 +8235,7 @@ void AddALinear(Entity linear, int power)
81938235
return null;
81948236
if (linears.Count == 1)
81958237
return AgainstAPowerOfALinear(polynomial, linears[0].Linear, linears[0].Power, angles, argument, x, needsAnIntegral: true) is var (one, _) && one is not null
8196-
? Functions.PartialFractions.Bare((constant * one).InnerSimplified)
8238+
? WrittenBack(Functions.PartialFractions.Bare((constant * one).InnerSimplified))
81978239
: null;
81988240
// Over each linear by partial fractions, and each term the one-linear question.
81998241
var denominator = linears.Aggregate((Entity)Number.Integer.One, (product, pair) => product * MathS.Pow(pair.Linear, pair.Power));
@@ -8210,7 +8252,7 @@ void AddALinear(Entity linear, int power)
82108252
sum += integral;
82118253
special |= integrals;
82128254
}
8213-
return special ? Functions.PartialFractions.Bare((constant * sum).InnerSimplified) : null;
8255+
return special ? WrittenBack(Functions.PartialFractions.Bare((constant * sum).InnerSimplified)) : null;
82148256
}
82158257

82168258
/// <summary>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A sine or a cosine over a quadratic or a binomial with compound coefficients, split over
16+
/// its roots with the coefficients named: <c>sin(a + b x)/(x ((d e - c f) x^2 + f))</c> was
17+
/// 11,876 characters, every coefficient expanded to the seventh degree; and one with a double
18+
/// root is a linear twice, where split over two equal roots it was NaN everywhere.
19+
/// <a href="https://github.com/asc-community/AngouriMath/issues/1877">#1877</a>,
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/1879">#1879</a>
21+
/// </summary>
22+
[Trait("Area", "Calculus")]
23+
public sealed class ATrigonometricOverAQuadraticWithCompoundCoefficientsIntegralTest
24+
{
25+
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
26+
{
27+
var integral = integrand.ToEntity().Integrate("x");
28+
Assert.DoesNotContain("integral(", integral.Stringize());
29+
Assert.DoesNotContain("NaN", integral.Stringize());
30+
31+
var derivative = integral.Substitute("C", 0).Differentiate("x");
32+
Entity original = integrand.ToEntity();
33+
foreach (var (name, value) in pins)
34+
{
35+
derivative = derivative.Substitute(name, value);
36+
original = original.Substitute(name, value);
37+
}
38+
39+
var compared = 0;
40+
foreach (var at in points)
41+
{
42+
var got = derivative.Substitute("x", at).EvalNumerical();
43+
var want = original.Substitute("x", at).EvalNumerical();
44+
if (got.IsNaN || want.IsNaN)
45+
continue;
46+
compared++;
47+
var difference = Math.Abs((double)(got - want).RealPart)
48+
+ Math.Abs((double)(got - want).ImaginaryPart);
49+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
50+
Assert.True(difference / scale < 1e-8,
51+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, "
52+
+ $"where the integrand is {want}");
53+
}
54+
Assert.True(compared >= 3,
55+
$"only {compared} of {points.Length} points were comparable for {integrand}");
56+
}
57+
58+
[Theory]
59+
[InlineData("sin(a+b*x)/(x*((d*e-c*f)*x^2+f))", 1500)]
60+
[InlineData("x*cos(a+b*x)/((d*e-c*f)*x^2+f)", 1500)]
61+
[InlineData("sin(a+b*x)/((c+d)*x^2+(e-f)*x+1)", 1500)]
62+
[InlineData("sin(a+b*x)/((c+d)*x^2+2*(c+d)*x+(c+d))", 1500)]
63+
[InlineData("sin(x)/(a*x^2+2*a*x+a)", 1500)]
64+
[InlineData("sin(a+b*x)/((d*e-c*f)*x^3+f)", 2000)]
65+
[InlineData("sin(a+b/sqrt(c+d*x))/(e+f*x)", 2000)]
66+
public void IsShortAndDifferentiatesBack(string integrand, int longest)
67+
{
68+
var length = integrand.ToEntity().Integrate("x").Stringize().Length;
69+
Assert.True(length <= longest, $"the antiderivative of {integrand} is {length} characters");
70+
var points = new[] { -1.4, -0.3, 0.8, 2.25 };
71+
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", 1.5), ("d", 1.25), ("e", 2), ("f", 3));
72+
DifferentiatesBack(integrand, points, ("a", -0.4), ("b", -1.5), ("c", 2), ("d", -0.5), ("e", -1), ("f", 2));
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)