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
12 changes: 12 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,18 @@ after it
|---|---|---|
| `"(a*c + b*c*x)^(-3-2*p)*(f + g*x)*(a^2 + 2*a*b*x + b^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)`; an answer with no value on the unreleased master | the antiderivative |

### A sine of a double argument beside the argument is written as a product

**Answers where there were none.** `csc(a + b x)^3 sin(2a + 2b x)^7` was declined: the rule that writes multiples
of one argument in it reads a numeric slope and bounds the degree it writes. A sine of the double is one product,
`sin(2A) = 2 sin(A) cos(A)`, whatever its power, and is written so beside functions of `A` for any slope
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"csc(a + b*x)^3*sin(2*a + 2*b*x)^7".ToEntity().Integrate("x")` | `integral(...)` | 127 characters |
| `"csc(a + b*x)*sin(2*a + 2*b*x)^8".ToEntity().Integrate("x")` | `integral(...)` | 134 characters |

### A sine or a cosine of a shifted argument is written in the other

**Answers where there were none.** `sin(a + b x) sec(c + b x)^3` was declined, with the rest of Rubi's 4.7.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12197,6 +12197,40 @@ node is Powf(var @base, var power) && @base.ContainsNode(u)
return back.Nodes.Any(node => node == MathS.NaN) ? null : back;
}

/// <summary>
/// A sine of twice an argument beside functions of the argument, written as the product it is:
/// <c>sin(2A) = 2 sin(A) cos(A)</c>, so that every trigonometric function is of <c>A</c>.
/// <c>csc(a + b x)^3 sin(2a + 2b x)^7</c> is <c>2^7 sin(a + b x)^4 cos(a + b x)^7</c>.
/// </summary>
/// <remarks>
/// The rule that writes multiples of one argument in it reads a numeric slope, and bounds the
/// degree it writes; a sine of the double is one product whatever its power, so it is written
/// here for any slope. Rubi's 4.7.1.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByWritingASineOfADoubleArgumentAsAProduct(Entity expr, Entity.Variable x, bool integrateByParts)
{
var arguments = new List<Entity>();
foreach (var node in expr.Nodes)
if (node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf && node.ContainsNode(x)
&& node.DirectChildren.First() is var argument && !arguments.Contains(argument))
arguments.Add(argument);
if (arguments.Count != 2)
return null;
foreach (var (single, twice) in new[] { (arguments[0], arguments[1]), (arguments[1], arguments[0]) })
{
if (!TreeAnalyzer.TryGetPolyLinear(single, x, out var slope, out _) || slope.ContainsNode(x)
|| (twice - 2 * single).Expand().InnerSimplified.Evaled is not Number.Complex { IsZero: true })
continue;
// Only sines of the double, so that each is one product.
if (expr.Nodes.Any(node => node is Cosf or Tanf or Cotanf or Secantf or Cosecantf && node.DirectChildren.First() == twice))
return null;
var rewritten = expr.Replace(node => node is Sinf(var a) && a == twice ? 2 * MathS.Sin(single) * MathS.Cos(single) : node);
return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);
}
return null;
}

/// <summary>
/// An integrand whose trigonometric functions have <b>different multiples</b> of one
/// argument — <c>sin(x)/cos(2x)</c>, <c>cos(x)/(sin(x) tan(x/2))</c> — rewritten so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// And the other way round: not a product of two arguments but anything else built
// from different multiples of one -- `sin(x)/cos(2x)`, `cos(x)/(sin(x) tan(x/2))` --
// rewritten to the one argument every trigonometric rule above reads.
// A sine of the double argument beside the argument: one product, for any slope.
if ((answer = IndefiniteIntegralSolver.SolveByWritingASineOfADoubleArgumentAsAProduct(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByUnifyingTrigonometricArguments(expr, x, integrateByParts)) is { }) return answer;
// The exponential substitution beside the other rewrites. It is also what integrates
// the hyperbolic functions, which are not nodes here but quotients of exponentials.
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 sine of twice an argument beside functions of the argument, written as the product
/// <c>sin(2A) = 2 sin(A) cos(A)</c>, for a symbolic slope too. Rubi's 4.7.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ASineOfADoubleArgumentIntegralTest
{
[Theory]
[InlineData("csc(a + b*x)^3*sin(2*a + 2*b*x)^7")]
[InlineData("csc(a + b*x)*sin(2*a + 2*b*x)^8")]
public void AsAProduct(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", 1.3).Substitute("b", 0.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
{
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