Symbolics Equality

I’m trying to extend some modules to work with Symbolics by getting rid of Bool conditionals and divide by zero checks etc. I’m trying to add some unit tests and I want to test if two symbolic equations are equivalent.

When I try and extend Base.:(==) by checking if the components of a composite type structure are == symbolically (I even throw in some simplify calls wrapping the LHS and RHS of the equals) it complains about non-Boolean being used in a Boolean context.

What’s the trick to see if two equations are the same symbolically so that my test cases can mean something.

Best Regards,
Allan

Use isequal.

Lol I know this is reviving a few years old thread, but I wanted to ask why this is still a thing with symbolics, as it generates a lot of comparability issues with other packages. Unless there is something within symbolics that utilizes == in a symbolic sense, this seems like a bad design choice?

For example, if I want to use symbolics with a package that runs ishermitian in a constructor, then it will just return an error. To fix this, it would require LinearAlgebra.jl, SparseArrays.jl and others to always be careful when doing any comparisons, and always use isequal instead of ==.

Worth adding that isequal isn’t always the safe fallback either:

julia> using SymbolicUtils

julia> pkgversion(SymbolicUtils)
v"4.46.1"

julia> @syms U
(U,)

julia> (-U)^2
(U^2)

julia> isequal(U*U, U^2)
true

julia> isequal((-U)^2, U^2)
false

Those two print the same modulo a pair of parentheses, and isequal still says no. It’s a malformed single-argument Mul node - simplify on it throws a BoundsError, and expand normalises it away. Details in SymbolicUtils.jl#1044.

My middle-school maths teacher passed away a few years ago. Small mercy: she never had to see the sign rule get an entry in a bug tracker. Though she did already ship the fix “expand it first” so expand((-U)^2) == U^2 would have been a one-line answer from her, in red pen, with a note about tidying up my brackets. :wink:

Edit: she was a PEGC.

Not really. Isequal and == are both structural comparisons, you don’t want these to include an internal simplify / expand as it could make them really slow. Even then, for complicated expressions, they might still fail. They will always be not completely reliable, so it is better to do an explicit form manipulation before checking equality. The bigger issue here is that they way == is defined in symbolics makes this unreliable comparison impossible as most packages use == and not isequal, which generates a lot of incompatibility issues and makes it hard to fine new uses for Symbolics.jl.

Edit: there is a good argument for modifying how power works so that it can distribute to any negative signs within an Mul automatically on creation. But I think that’s separate from the compatibility issues the current setup introduces.

This is standard Julia. It’s how the == and isequal docstring tell you to overload things. == does not generally return booleans, isequal does.

There’s a few thousand uses of this :sweat_smile:, tracing standard numerical functions requires it for example.

I’m a bit confused on this part, because the documentation states the output for Base.:(==) is Bool or missing. It says that type promotion should be done to facilitate this comparison, and missing is only returned in special cases. The current implementation in symbolics returns Num, which breaks with this convention. It’s this break with convention which makes compatibility with other packages in the julia ecosystem problematic, because if two symbolic expressions end up in a if a==b situation, it throws an error instead of returning a true/false, and most packages use this instead of isequal, because something like isequal(0.0,-0.0) returns false while -0.0 == 0.0 returns true.

Let me phase this a bit better, are their routines in symbolics that are built around handling a the Num output of x==b? If not, then I don’t see any reason symbolics should break with the Base.:(==) convention outlined in the documentation.

No, it’s because routines outside of Symbolics use == so we have to match that in order to trace.

So, I’m still a bit confused on why that requires Base.:(==) to behave differently for Num instead of following outputs outlined in the documentation.

Because Num is a tracer, and if it doesn’t build a symbolic expression from it then it would not be able to continue through branches