diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.ExpandFactorize.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.ExpandFactorize.cs
index 34b418076..a6c6867fc 100644
--- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.ExpandFactorize.cs
+++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.ExpandFactorize.cs
@@ -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,
diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs
index 27f154766..8f17ff7d2 100644
--- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs
+++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs
@@ -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.
diff --git a/Sources/Tests/UnitTests/PatternsTest/PowerProductBranchTest.cs b/Sources/Tests/UnitTests/PatternsTest/PowerProductBranchTest.cs
new file mode 100644
index 000000000..c3ba52834
--- /dev/null
+++ b/Sources/Tests/UnitTests/PatternsTest/PowerProductBranchTest.cs
@@ -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
+{
+ ///
+ /// {1}^n * {2}^n = ({1} * {2})^n was applied for every exponent, and it is false
+ /// across the branch cuts: sqrt(-1) * sqrt(-1) is i * i = -1 while
+ /// sqrt((-1)(-1)) is sqrt(1) = 1.
+ /// https://github.com/asc-community/AngouriMath/issues/801
+ ///
+ ///
+ /// 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.
+ ///
+ [Trait("Area", "PatternsTest")]
+ public sealed class PowerProductBranchTest
+ {
+ ///
+ /// 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.
+ ///
+ [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()}");
+ }
+ }
+
+ ///
+ /// The quotient form has the same hole and is deliberately still open:
+ /// sqrt(2) / sqrt(-3) is -0.8165i while (2 / -3)^(1/2) is
+ /// +0.8165i. 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 (x^2 + 1)^x / (x^2)^x 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
+ ///
+ [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()}");
+ }
+
+ ///
+ /// 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.
+ ///
+ [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);
+ }
+ }
+}