Simplify rewrites sqrt(x) * sqrt(y) to sqrt(x * y), which is false across the branch cuts.
"sqrt(x) * sqrt(y)".ToEntity().Simplify() // sqrt(x * y)
// at x = y = -1
"sqrt(x) * sqrt(y)".ToEntity().Substitute("x", -1).Substitute("y", -1).Evaled // -1
"sqrt(x * y)".ToEntity().Substitute("x", -1).Substitute("y", -1).Evaled // 1
sqrt(-1) * sqrt(-1) is i * i = -1. sqrt((-1)(-1)) is sqrt(1) = 1. The rewrite turns one into the other.
Where
Patterns.PowerRules:
// {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),
Divf(Powf(var any1, var any3), Powf(var any2, var any3a)) when any3 == any3a => new Powf(any1 / any2, any3),
Both are unconditional. (ab)^n = a^n b^n holds for a whole n whatever the signs, and for positive real bases whatever the exponent, but not for a fractional n over the complex plane in general — the two sides can differ by a full turn of the argument.
The same shape as #752
#752 was the same mistake in two neighbouring rules — sqrt(x^2) -> x and sqrt(-x) -> i*sqrt(x) — and was fixed by asking the condition each already required. The ({}^{})^{} rule immediately above these two now carries exactly the guard that is missing here:
Powf(Powf(var any1, var any2), var any3)
when any3 is Integer || any1.Evaled is Real { IsPositive: true }
=> new Powf(any1, any2 * any3),
How it was found
Not by a harness. Writing a perfect-square collapse for #176, the rule needed to decide whether a cross term is twice the product of two roots, and asked Simplify — which answered using this rewrite and made the collapse fire on x + 2*sqrt(x*y) + y, whose collapsed form is -4 at x = y = -1 where the sum is 0. That fix now gates the symbolic match on a numeric check rather than trusting the answer, so the wrong collapse no longer ships; this issue is the cause underneath it.
simpsweep does not catch it because it generates single-variable expressions, and this needs two variables both taken negative at once.
Suggested fix
The same guard the rule above carries: fire when the exponent is an integer, or when both bases are decidably positive reals. sqrt(2) * sqrt(3) -> sqrt(6) keeps working; sqrt(x) * sqrt(y) stops.
Simplifyrewritessqrt(x) * sqrt(y)tosqrt(x * y), which is false across the branch cuts.sqrt(-1) * sqrt(-1)isi * i = -1.sqrt((-1)(-1))issqrt(1) = 1. The rewrite turns one into the other.Where
Patterns.PowerRules:Both are unconditional.
(ab)^n = a^n b^nholds for a wholenwhatever the signs, and for positive real bases whatever the exponent, but not for a fractionalnover the complex plane in general — the two sides can differ by a full turn of the argument.The same shape as #752
#752 was the same mistake in two neighbouring rules —
sqrt(x^2) -> xandsqrt(-x) -> i*sqrt(x)— and was fixed by asking the condition each already required. The({}^{})^{}rule immediately above these two now carries exactly the guard that is missing here:How it was found
Not by a harness. Writing a perfect-square collapse for #176, the rule needed to decide whether a cross term is twice the product of two roots, and asked
Simplify— which answered using this rewrite and made the collapse fire onx + 2*sqrt(x*y) + y, whose collapsed form is-4atx = y = -1where the sum is0. That fix now gates the symbolic match on a numeric check rather than trusting the answer, so the wrong collapse no longer ships; this issue is the cause underneath it.simpsweepdoes not catch it because it generates single-variable expressions, and this needs two variables both taken negative at once.Suggested fix
The same guard the rule above carries: fire when the exponent is an integer, or when both bases are decidably positive reals.
sqrt(2) * sqrt(3) -> sqrt(6)keeps working;sqrt(x) * sqrt(y)stops.