Symbolics.jl icon indicating copy to clipboard operation
Symbolics.jl copied to clipboard

Rules work with @syms but not with @variables

Open ebelnikola opened this issue 9 months ago • 4 comments

Hi!

I noticed the following behavior similar to what was mentioned in this issue. When I apply a rule to a function defined via "@variables" it does not work, while with "@syms" everything is fine. Here is a minimal example:

using Symbolics
@variables a(..)
@syms b(i1, i2)::Real
println("types are the same: ", (a(2, 1) |> typeof) == (b(2, 1) |> typeof)) # prints true
ra = @rule a(~i1, ~i2) => a(~i2, ~i1) where {~i1>~i2}
rb = @rule b(~i1, ~i2) => b(~i2, ~i1) where {~i1>~i2}
println("result of the rule application on a: ", ra(a(2, 1))) # prints nothing
println("result of the rule application on b: ", rb(b(2, 1))) # prints b(1,2)

There is no documentation on pattern matching, so I don't know if this is intended or not.

ebelnikola avatar Feb 10 '25 16:02 ebelnikola

It needs to unwrap. It would be nice if we made them auto-unwrap and wrap.

ChrisRackauckas avatar Feb 10 '25 16:02 ChrisRackauckas

What should be unwrapped exactly? I tried ra(Symbolics.unwrap(a(2, 1))). This does not change the result. Also, note that there is the test showing that a(2,1) and b(2,1) are of the same type.

ebelnikola avatar Feb 10 '25 16:02 ebelnikola

Sounds like there's something deeper going on here then.

ChrisRackauckas avatar Feb 10 '25 16:02 ChrisRackauckas

Hello, i explored this issue but I have some question I cannot resolve myself. Here are my findings and questions:

This rule application doesnt work because a and b are of different types. As pointed out by @ebelnikola a(2,1) and b(2,1) are of the same type, that is:

julia> typeof(a(2,1))
SymbolicUtils.BasicSymbolic{Real}

, but:

julia> typeof(a)
Symbolics.CallWithMetadata{SymbolicUtils.FnType{Tuple, Real}, Base.ImmutableDict{DataType, Any}}

julia> typeof(b)
SymbolicUtils.BasicSymbolic{SymbolicUtils.FnType{Tuple{Number, Number}, Real, Nothing}}

The code responsible for this is the function matcher from SymbolicUtils: https://github.com/JuliaSymbolics/SymbolicUtils.jl/blob/2f8d4faed52a4e40ce419261a377c86fd1b0c889/src/matchers.jl#L8C1-L13C4 it is called when the rule is definied, and returns another function that gets assigns to the rule object, and gets called every time that the rule gets applied. The variable val represent the left hand side of the rule (the thing we need to check against). In this particular example is of type Symbolics.CallWithMetadata, and the rule application fails bc it doesnt match with car(data) of type SymbolicUtils.BasicSymbolic{SymbolicUtils.FnType{Tuple, Real}} (data is what is passed when calling the rule, so a(2,1) in this case).

One potential fix could be to check whether the left hand side of a rule is a Symbolics.CallWithMetadata, and in that case use instead of val its argumet val.f, something like this:

function matcher(val::Any)
    iscall(val) && return term_matcher(val)

    if # val isa Symbolics.CallWithMetadata
        val = val.f
    end

    function literal_matcher(next, data, bindings)
        islist(data) && isequal(car(data), val) ? next(bindings, 1) : nothing
    end
end

. But as you can see i put a comment instead of the if condition because i get the error:

ERROR: UndefVarError: `Symbolics` not defined in `SymbolicUtils`
Suggestion: check for spelling errors or missing imports.
Hint: a global variable of this name also exists in Symbolics.
Stacktrace:
 [1] matcher(val::Symbolics.CallWithMetadata{SymbolicUtils.FnType{Tuple, Real}, Base.ImmutableDict{DataType, Any}})
   @ SymbolicUtils ~/.julia/dev/SymbolicUtils/src/matchers.jl:13
 [2] term_matcher(term::SymbolicUtils.BasicSymbolic{Any})
   @ SymbolicUtils ~/.julia/dev/SymbolicUtils/src/matchers.jl:117
 [3] matcher(val::SymbolicUtils.BasicSymbolic{Any})
   @ SymbolicUtils ~/.julia/dev/SymbolicUtils/src/matchers.jl:11
 [4] top-level scope
   @ ~/.julia/dev/SymbolicUtils/src/rule.jl:321

Questions:

  • What is the correct way to reference Symbolics.CallWithMetadata type from SymbolicUtils?
  • is this a good fix or just a patch and something deeper needs to be changed?

PS: with this code:

function matcher(val::Any)
    iscall(val) && return term_matcher(val)

    try
        val = val.f
    catch
    end

    function literal_matcher(next, data, bindings)
        islist(data) && isequal(car(data), val) ? next(bindings, 1) : nothing
    end
end

it works:

julia> ra(a(2, 1))
a(1, 2)

Bumblebee00 avatar Apr 08 '25 18:04 Bumblebee00