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
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,18 @@ internal static partial class Patterns
Minusf(var any1, var any1a) when any1 == any1a => 0,

// a ^ b * c ^ b = (a * c) ^ b
Mulf(Powf(var any1, var any2), Powf(var any3, var any2a)) when any2 == any2a => new Powf(any1 * any3, any2),
//
// Guarded like its twin in PowerRules, which is where this identity is written
// out and explained: true for a whole b whatever the signs, and for positive
// real bases whatever the exponent, and false outside those two --
// sqrt(x) * sqrt(y) became sqrt(x * y), which at x = y = -1 is 1 where the
// product is -1. https://github.com/asc-community/AngouriMath/issues/801
Mulf(Powf(var any1, var any2), Powf(var any3, var any2a))
when any2 == any2a
&& (any2 is Integer
|| (any1.Evaled is Real { IsPositive: true }
&& any3.Evaled is Real { IsPositive: true }))
=> new Powf(any1 * any3, any2),

Sumf or Minusf when CollectCommonFactors(x) is { } collected => collected,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,30 @@ expr is Sumf(var any1, Mulf(Real { IsNegative: true } const1, var any2))
=> new Powf(any1, any2 * any3),

// {1} ^ n * {2} ^ n = ({1} * {2}) ^ n
Mulf(Powf(var any1, var any3), Powf(var any2, var any3a)) when any3 == any3a => new Powf(any1 * any2, any3),
//
// The same condition the ({}^{})^{} rule above carries, and missing here for the
// same reason -- both were written unconditionally and #752 only looked at one of
// them. True for a whole n whatever the signs, since a^3 b^3 is (ab)(ab)(ab), and
// for positive real bases whatever the exponent. Outside those two the two sides
// can differ by a full turn of the argument:
//
// sqrt(x) * sqrt(y) came back as sqrt(x * y), and at x = y = -1 the first
// is i * i = -1 while the second is sqrt(1) = 1
//
// https://github.com/asc-community/AngouriMath/issues/801
Mulf(Powf(var any1, var any3), Powf(var any2, var any3a))
when any3 == any3a
&& (any3 is Integer
|| (any1.Evaled is Real { IsPositive: true }
&& any2.Evaled is Real { IsPositive: true }))
=> new Powf(any1 * any2, any3),
// The quotient form has the same hole -- sqrt(2) / sqrt(-3) is -0.8165i while
// (2 / -3)^(1/2) is +0.8165i -- and is deliberately left unguarded, because
// guarding it costs answers rather than only shapes. It is what lets the limit
// machinery read a 1^oo out of a quotient, so `(x^2 + 1)^x / (x^2)^x` stops
// being answered at all, which is the whole of #739 and #740. Which of the two
// to prefer is a maintainer's call and is filed separately rather than taken
// here. https://github.com/asc-community/AngouriMath/issues/802
Divf(Powf(var any1, var any3), Powf(var any2, var any3a)) when any3 == any3a => new Powf(any1 / any2, any3),

// {1} ^ n / {2} ^ (c * n) = ({1} / {2} ^ c) ^ n, and the same the other way up.
Expand Down
90 changes: 90 additions & 0 deletions Sources/Tests/UnitTests/PatternsTest/PowerProductBranchTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// Copyright (c) 2019-2022 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;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.PatternsTest
{
/// <summary>
/// <c>{1}^n * {2}^n = ({1} * {2})^n</c> was applied for every exponent, and it is false
/// across the branch cuts: <c>sqrt(-1) * sqrt(-1)</c> is <c>i * i = -1</c> while
/// <c>sqrt((-1)(-1))</c> is <c>sqrt(1) = 1</c>.
/// https://github.com/asc-community/AngouriMath/issues/801
/// </summary>
/// <remarks>
/// The same mistake as https://github.com/asc-community/AngouriMath/issues/752, in two
/// rules that sit immediately below the one #752 fixed and were not looked at then.
/// </remarks>
[Trait("Area", "PatternsTest")]
public sealed class PowerProductBranchTest
{
/// <summary>
/// The property, checked where a branch error shows: both bases negative. Compared
/// against the expression it came from rather than against an expected form, since
/// what matters is that simplifying did not change the value.
/// </summary>
[Theory]
[InlineData("x ^ (1/2) * y ^ (1/2)")]
[InlineData("sqrt(x) * sqrt(y)")]
[InlineData("x ^ (1/3) * y ^ (1/3)")]
[InlineData("x ^ (3/2) * y ^ (3/2)")]
public void SimplifyingKeepsTheValueAtNegativeBases(string expr)
{
var simplified = expr.ToEntity().Simplify();
foreach (var (x, y) in new[] { (-1.0, -1.0), (-2.0, -3.0), (-0.5, -4.0), (-1.5, 2.5) })
{
var before = expr.ToEntity().Substitute("x", x).Substitute("y", y).EvalNumerical();
var after = simplified.Substitute("x", x).Substitute("y", y).EvalNumerical();
var difference = Math.Abs(before.RealPart.EDecimal.ToDouble() - after.RealPart.EDecimal.ToDouble())
+ Math.Abs(before.ImaginaryPart.EDecimal.ToDouble() - after.ImaginaryPart.EDecimal.ToDouble());
Assert.True(difference < 1e-9,
$"{expr} simplified to {simplified.Stringize()}, which at x = {x}, y = {y} "
+ $"is {after.Stringize()} rather than {before.Stringize()}");
}
}

/// <summary>
/// The quotient form has the same hole and is deliberately still open:
/// <c>sqrt(2) / sqrt(-3)</c> is <c>-0.8165i</c> while <c>(2 / -3)^(1/2)</c> is
/// <c>+0.8165i</c>. It is left because guarding it costs *answers* rather than only
/// shapes -- it is what lets the limit machinery read a 1^oo out of a quotient, and
/// with it guarded <c>(x^2 + 1)^x / (x^2)^x</c> stops having a limit at all, which
/// is the whole of #739 and #740. Pinned here so that the day it is fixed, this
/// test fails and says where to look.
/// https://github.com/asc-community/AngouriMath/issues/802
/// </summary>
[Fact]
public void TheQuotientFormIsStillWrongAndThatIsRecorded()
{
var simplified = "sqrt(x) / sqrt(y)".ToEntity().Simplify();
var before = "sqrt(x) / sqrt(y)".ToEntity().Substitute("x", 2).Substitute("y", -3).EvalNumerical();
var after = simplified.Substitute("x", 2).Substitute("y", -3).EvalNumerical();
Assert.True(
Math.Abs(before.ImaginaryPart.EDecimal.ToDouble() - after.ImaginaryPart.EDecimal.ToDouble()) > 1e-9,
$"the quotient form now agrees at x = 2, y = -3 -- #802 looks fixed, so this test "
+ $"should become an assertion that it stays fixed. Simplified: {simplified.Stringize()}");
}

/// <summary>
/// The rule must keep firing where it is sound, or this would be a fix that removes
/// an answer. A whole exponent is safe whatever the signs, and positive bases are
/// safe whatever the exponent.
/// </summary>
[Theory]
[InlineData("sqrt(2) * sqrt(3)", "sqrt(6)")]
[InlineData("x ^ 2 * y ^ 2", "(x * y) ^ 2")]
[InlineData("x ^ 3 * y ^ 3", "(x * y) ^ 3")]
public void TheSoundCasesStillGather(string expr, string expected)
{
var simplified = expr.ToEntity().Simplify();
Assert.Equal(expected.ToEntity().Simplify(), simplified);
}
}
}
Loading